/* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2013 Nexenta Systems, Inc. All rights reserved. * Copyright 2023 RackTop Systems, Inc. */ /* * SMB server interfaces for ACL conversion (smb_acl_...) * * There are two variants of this interface: * This is the kernel version. See also: * $SRC/lib/smbsrv/libsmb/common/smb_acl.c */ #include #include #include #include #include #include #include #define ACE_FD_INHERIT_ACE (ACE_FILE_INHERIT_ACE | ACE_DIRECTORY_INHERIT_ACE) #define ZACE_IS_OWNER(zace) ((zace->a_flags & ACE_TYPE_FLAGS) == ACE_OWNER) #define ZACE_IS_OWNGRP(zace) \ ((zace->a_flags & ACE_TYPE_FLAGS) == (ACE_IDENTIFIER_GROUP|ACE_GROUP)) #define ZACE_IS_USER(zace) \ (((zace->a_flags & ACE_TYPE_FLAGS) == 0) || (ZACE_IS_OWNER(zace))) #define ZACE_IS_GROUP(zace) (zace->a_flags & ACE_IDENTIFIER_GROUP) #define ZACE_IS_EVERYONE(zace) (zace->a_flags & ACE_EVERYONE) #define ZACE_IS_PROPAGATE(zace) \ ((zace->a_flags & ACE_NO_PROPAGATE_INHERIT_ACE) == 0) #define ZACE_IS_CREATOR_OWNER(zace) \ (ZACE_IS_USER(zace) && (zace->a_who == IDMAP_WK_CREATOR_OWNER_UID)) #define ZACE_IS_CREATOR_GROUP(zace) \ (ZACE_IS_GROUP(zace) && (zace->a_who == IDMAP_WK_CREATOR_GROUP_GID)) #define ZACE_IS_CREATOR(zace) \ (ZACE_IS_CREATOR_OWNER(zace) || ZACE_IS_CREATOR_GROUP(zace)) /* * ACE groups within a DACL * * This is from lower to higher ACE order priority */ #define SMB_AG_START 0 #define SMB_AG_ALW_INHRT 0 #define SMB_AG_DNY_INHRT 1 #define SMB_AG_ALW_DRCT 2 #define SMB_AG_DNY_DRCT 3 #define SMB_AG_NUM 4 #define DEFAULT_DACL_ACENUM 2 /* * Default ACL: * owner: full access * SYSTEM: full access */ #ifdef _KERNEL static const ace_t default_dacl[DEFAULT_DACL_ACENUM] = { { (uid_t)-1, ACE_ALL_PERMS, 0, ACE_ACCESS_ALLOWED_ACE_TYPE }, { IDMAP_WK_LOCAL_SYSTEM_GID, ACE_ALL_PERMS, ACE_IDENTIFIER_GROUP, ACE_ACCESS_ALLOWED_ACE_TYPE } }; #endif /* _KERNEL */ /* * Note: * * smb_acl_xxx functions work with smb_acl_t which represents the CIFS format * smb_fsacl_xxx functions work with acl_t which represents the Solaris native * format */ static idmap_stat smb_fsacl_getsids(smb_idmap_batch_t *, acl_t *); static acl_t *smb_fsacl_null_empty(boolean_t); #ifdef _KERNEL static int smb_fsacl_inheritable(acl_t *, int); static void smb_ace_inherit(ace_t *, ace_t *, int, uid_t, gid_t); #endif /* _KERNEL */ static boolean_t smb_ace_isvalid(smb_ace_t *, int); static uint16_t smb_ace_len(smb_ace_t *); static uint32_t smb_ace_mask_g2s(uint32_t); static uint16_t smb_ace_flags_tozfs(uint8_t); static uint8_t smb_ace_flags_fromzfs(uint16_t); static boolean_t smb_ace_wellknown_update(const char *, ace_t *); smb_acl_t * smb_acl_alloc(uint8_t revision, uint16_t bsize, uint16_t acecnt) { smb_acl_t *acl; int size; size = sizeof (smb_acl_t) + (acecnt * sizeof (smb_ace_t)); acl = kmem_zalloc(size, KM_SLEEP); acl->sl_revision = revision; acl->sl_bsize = bsize; acl->sl_acecnt = acecnt; acl->sl_aces = (smb_ace_t *)(acl + 1); list_create(&acl->sl_sorted, sizeof (smb_ace_t), offsetof(smb_ace_t, se_sln)); return (acl); } void smb_acl_free(smb_acl_t *acl) { int i, size; void *ace; if (acl == NULL) return; for (i = 0; i < acl->sl_acecnt; i++) smb_sid_free(acl->sl_aces[i].se_sid); while ((ace = list_head(&acl->sl_sorted)) != NULL) list_remove(&acl->sl_sorted, ace); list_destroy(&acl->sl_sorted); size = sizeof (smb_acl_t) + (acl->sl_acecnt * sizeof (smb_ace_t)); kmem_free(acl, size); } /* * smb_acl_len * * Returns the size of given ACL in bytes. Note that this * is not an in-memory size, it's the ACL's size as it would * appear on the wire */ uint16_t smb_acl_len(smb_acl_t *acl) { return ((acl) ? acl->sl_bsize : 0); } boolean_t smb_acl_isvalid(smb_acl_t *acl, int which_acl) { int i; if (acl->sl_bsize < SMB_ACL_HDRSIZE) return (B_FALSE); if (acl->sl_revision != ACL_REVISION) { /* * we are rejecting ACLs with object-specific ACEs for now */ return (B_FALSE); } for (i = 0; i < acl->sl_acecnt; i++) { if (!smb_ace_isvalid(&acl->sl_aces[i], which_acl)) return (B_FALSE); } return (B_TRUE); } /* * smb_acl_sort * * Sorts the given ACL in place if it needs to be sorted. * * The following is an excerpt from MSDN website. * * Order of ACEs in a DACL * * For Windows NT versions 4.0 and earlier, the preferred order of ACEs * is simple: In a DACL, all access-denied ACEs should precede any * access-allowed ACEs. * * For Windows 2000 or later, the proper order of ACEs is more complicated * because of the introduction of object-specific ACEs and automatic * inheritance. * * The following describes the preferred order: * * To ensure that noninherited ACEs have precedence over inherited ACEs, * place all noninherited ACEs in a group before any inherited ACEs. This * ordering ensures, for example, that a noninherited access-denied ACE * is enforced regardless of any inherited ACE that allows access. * Within the groups of noninherited ACEs and inherited ACEs, order ACEs * according to ACE type, as the following shows: * . Access-denied ACEs that apply to the object itself * . Access-denied ACEs that apply to a subobject of the * object, such as a property set or property * . Access-allowed ACEs that apply to the object itself * . Access-allowed ACEs that apply to a subobject of the object * * So, here is the desired ACE order * * deny-direct, allow-direct, deny-inherited, allow-inherited * * Of course, not all ACE types are required in an ACL. */ void smb_acl_sort(smb_acl_t *acl) { list_t ace_grps[SMB_AG_NUM]; list_t *alist; smb_ace_t *ace; uint8_t ace_flags; int ag, i; ASSERT(acl); if (acl->sl_acecnt == 0) { /* * ACL with no entry is a valid ACL and it means * no access for anybody. */ return; } for (i = SMB_AG_START; i < SMB_AG_NUM; i++) { list_create(&ace_grps[i], sizeof (smb_ace_t), offsetof(smb_ace_t, se_sln)); } for (i = 0, ace = acl->sl_aces; i < acl->sl_acecnt; ++i, ace++) { ace_flags = ace->se_hdr.se_flags; switch (ace->se_hdr.se_type) { case ACCESS_DENIED_ACE_TYPE: ag = (ace_flags & INHERITED_ACE) ? SMB_AG_DNY_INHRT : SMB_AG_DNY_DRCT; break; case ACCESS_ALLOWED_ACE_TYPE: ag = (ace_flags & INHERITED_ACE) ? SMB_AG_ALW_INHRT : SMB_AG_ALW_DRCT; break; default: /* * This is the lowest priority group so we put * evertything unknown here. */ ag = SMB_AG_ALW_INHRT; break; } /* Add the ACE to the selected group */ list_insert_tail(&ace_grps[ag], ace); } /* * start with highest priority ACE group and append * the ACEs to the ACL. */ for (i = SMB_AG_NUM - 1; i >= SMB_AG_START; i--) { alist = &ace_grps[i]; while ((ace = list_head(alist)) != NULL) { list_remove(alist, ace); list_insert_tail(&acl->sl_sorted, ace); } list_destroy(alist); } } /* * Error handling call-back for smb_idmap_batch_getmappings. * Would be nice if this could report the path, but that's not * passed down here. For now, use a dtrace fbt probe here. */ static void smb_acl_bgm_error(smb_idmap_batch_t *sib, smb_idmap_t *sim) { if ((sib->sib_flags & SMB_IDMAP_SKIP_ERRS) != 0) return; if ((sib->sib_flags & SMB_IDMAP_ID2SID) != 0) { /* * Note: The ID and type we asked idmap to map * were saved in *sim_id and sim_idtype. */ int id = (sim->sim_id == NULL) ? -1 : (int)*sim->sim_id; cmn_err(CE_WARN, "!smb_acl: Can't get SID for " "ID=%d type=%d, status=%d", id, sim->sim_idtype, sim->sim_stat); } if ((sib->sib_flags & SMB_IDMAP_SID2ID) != 0) { cmn_err(CE_WARN, "!smb_acl: Can't get ID for " "SID %s-%u, status=%d", sim->sim_domsid, sim->sim_rid, sim->sim_stat); } } /* * smb_acl_from_zfs * * Converts given ZFS ACL to a Windows ACL. * * A pointer to allocated memory for the Win ACL will be * returned upon successful conversion. */ smb_acl_t * smb_acl_from_zfs(acl_t *zacl) { ace_t *zace; int numaces; smb_acl_t *acl; smb_ace_t *ace; smb_idmap_batch_t sib; smb_idmap_t *sim; idmap_stat idm_stat; idm_stat = smb_idmap_batch_create(&sib, zacl->acl_cnt, SMB_IDMAP_ID2SID); if (idm_stat != IDMAP_SUCCESS) return (NULL); /* * Note that smb_fsacl_getsids sets up references in * sib.sib_maps to the zace->a_who fields that live * until smb_idmap_batch_destroy is called. */ if (smb_fsacl_getsids(&sib, zacl) != IDMAP_SUCCESS) { smb_idmap_batch_destroy(&sib); return (NULL); } acl = smb_acl_alloc(ACL_REVISION, SMB_ACL_HDRSIZE, zacl->acl_cnt); sim = sib.sib_maps; for (numaces = 0, zace = zacl->acl_aclp; numaces < zacl->acl_cnt; zace++, numaces++, sim++) { ASSERT(sim->sim_sid); if (sim->sim_sid == NULL) { smb_acl_free(acl); acl = NULL; break; } ace = &acl->sl_aces[numaces]; ace->se_hdr.se_type = zace->a_type; ace->se_hdr.se_flags = smb_ace_flags_fromzfs(zace->a_flags); ace->se_mask = zace->a_access_mask; ace->se_sid = smb_sid_dup(sim->sim_sid); ace->se_hdr.se_bsize = smb_ace_len(ace); acl->sl_bsize += ace->se_hdr.se_bsize; } smb_idmap_batch_destroy(&sib); return (acl); } /* * smb_acl_to_zfs * * Converts given Windows ACL to a ZFS ACL. * * fs_acl will contain a pointer to the created ZFS ACL. * The allocated memory should be freed by calling * smb_fsacl_free(). * * Since the output parameter, fs_acl, is allocated in this * function, the caller has to make sure *fs_acl is NULL which * means it's not pointing to any memory. */ uint32_t smb_acl_to_zfs(smb_acl_t *acl, uint32_t flags, int which_acl, acl_t **fs_acl) { smb_ace_t *ace; acl_t *zacl; ace_t *zace; smb_idmap_batch_t sib; smb_idmap_t *sim; idmap_stat idm_stat; char *sidstr; int i; ASSERT(fs_acl); ASSERT(*fs_acl == NULL); if (acl && !smb_acl_isvalid(acl, which_acl)) return (NT_STATUS_INVALID_ACL); if ((acl == NULL) || (acl->sl_acecnt == 0)) { if (which_acl == SMB_DACL_SECINFO) { *fs_acl = smb_fsacl_null_empty(acl == NULL); } return (NT_STATUS_SUCCESS); } idm_stat = smb_idmap_batch_create(&sib, acl->sl_acecnt, SMB_IDMAP_SID2ID); if (idm_stat != IDMAP_SUCCESS) return (NT_STATUS_INTERNAL_ERROR); sidstr = kmem_alloc(SMB_SID_STRSZ, KM_SLEEP); zacl = smb_fsacl_alloc(acl->sl_acecnt, flags); zace = zacl->acl_aclp; ace = acl->sl_aces; sim = sib.sib_maps; for (i = 0; i < acl->sl_acecnt; i++, zace++, ace++, sim++) { zace->a_type = ace->se_hdr.se_type & ACE_ALL_TYPES; zace->a_access_mask = smb_ace_mask_g2s(ace->se_mask); zace->a_flags = smb_ace_flags_tozfs(ace->se_hdr.se_flags); zace->a_who = (uid_t)-1; smb_sid_tostr(ace->se_sid, sidstr); if (!smb_ace_wellknown_update(sidstr, zace)) { sim->sim_id = &zace->a_who; idm_stat = smb_idmap_batch_getid(sib.sib_idmaph, sim, ace->se_sid, SMB_IDMAP_UNKNOWN); if (idm_stat != IDMAP_SUCCESS) { kmem_free(sidstr, SMB_SID_STRSZ); smb_fsacl_free(zacl); smb_idmap_batch_destroy(&sib); return (NT_STATUS_INTERNAL_ERROR); } } } kmem_free(sidstr, SMB_SID_STRSZ); idm_stat = smb_idmap_batch_getmappings(&sib, smb_acl_bgm_error); if (idm_stat != IDMAP_SUCCESS) { smb_fsacl_free(zacl); smb_idmap_batch_destroy(&sib); return (NT_STATUS_NONE_MAPPED); } /* * Set the ACEs group flag based on the type of ID returned. */ zace = zacl->acl_aclp; ace = acl->sl_aces; sim = sib.sib_maps; for (i = 0; i < acl->sl_acecnt; i++, zace++, ace++, sim++) { if (zace->a_who == (uid_t)-1) continue; if (sim->sim_idtype == SMB_IDMAP_GROUP) zace->a_flags |= ACE_IDENTIFIER_GROUP; } smb_idmap_batch_destroy(&sib); *fs_acl = zacl; return (NT_STATUS_SUCCESS); } static boolean_t smb_ace_wellknown_update(const char *sid, ace_t *zace) { struct { char *sid; uint16_t flags; } map[] = { { NT_WORLD_SIDSTR, ACE_EVERYONE }, { NT_BUILTIN_CURRENT_OWNER_SIDSTR, ACE_OWNER }, { NT_BUILTIN_CURRENT_GROUP_SIDSTR, (ACE_GROUP | ACE_IDENTIFIER_GROUP) }, }; int i; for (i = 0; i < (sizeof (map) / sizeof (map[0])); ++i) { if (strcmp(sid, map[i].sid) == 0) { zace->a_flags |= map[i].flags; return (B_TRUE); } } return (B_FALSE); } /* * smb_fsacl_getsids * * Batch all the uid/gid in given ZFS ACL to get their corresponding SIDs. * Note: sib is type SMB_IDMAP_ID2SID, zacl->acl_cnt entries. */ static idmap_stat smb_fsacl_getsids(smb_idmap_batch_t *sib, acl_t *zacl) { ace_t *zace; idmap_stat idm_stat; smb_idmap_t *sim; uid_t id; int i, idtype; sim = sib->sib_maps; for (i = 0, zace = zacl->acl_aclp; i < zacl->acl_cnt; zace++, i++, sim++) { id = (uid_t)-1; /* some types do not need id */ switch (zace->a_flags & ACE_TYPE_FLAGS) { case ACE_OWNER: idtype = SMB_IDMAP_OWNERAT; break; case (ACE_GROUP | ACE_IDENTIFIER_GROUP): /* owning group */ idtype = SMB_IDMAP_GROUPAT; break; case ACE_IDENTIFIER_GROUP: /* regular group */ idtype = SMB_IDMAP_GROUP; id = zace->a_who; /* for smb_acl_bgm_error() ID2SID */ sim->sim_id = &zace->a_who; break; case ACE_EVERYONE: idtype = SMB_IDMAP_EVERYONE; break; default: /* user entry */ idtype = SMB_IDMAP_USER; id = zace->a_who; /* for smb_acl_bgm_error() ID2SID */ sim->sim_id = &zace->a_who; } idm_stat = smb_idmap_batch_getsid(sib->sib_idmaph, sim, id, idtype); if (idm_stat != IDMAP_SUCCESS) { return (idm_stat); } } idm_stat = smb_idmap_batch_getmappings(sib, smb_acl_bgm_error); return (idm_stat); } /* * smb_fsacl_null_empty * * NULL DACL means everyone full-access * Empty DACL means everyone full-deny * * ZFS ACL must have at least one entry so smb server has * to simulate the aforementioned expected behavior by adding * an entry in case the requested DACL is null or empty. Adding * a everyone full-deny entry has proved to be problematic in * tests since a deny entry takes precedence over allow entries. * So, instead of adding a everyone full-deny, an owner ACE with * owner implicit permissions will be set. */ static acl_t * smb_fsacl_null_empty(boolean_t null) { acl_t *zacl; ace_t *zace; zacl = smb_fsacl_alloc(1, ACL_AUTO_INHERIT); zace = zacl->acl_aclp; zace->a_type = ACE_ACCESS_ALLOWED_ACE_TYPE; if (null) { zace->a_access_mask = ACE_ALL_PERMS; zace->a_flags = ACE_EVERYONE; } else { zace->a_access_mask = ACE_READ_ACL | ACE_WRITE_ACL | ACE_READ_ATTRIBUTES; zace->a_flags = ACE_OWNER; } return (zacl); } /* * FS ACL (acl_t) Functions */ acl_t * smb_fsacl_alloc(int acenum, int flags) { acl_t *acl; acl = acl_alloc(ACE_T); acl->acl_cnt = acenum; acl->acl_aclp = kmem_zalloc(acl->acl_entry_size * acenum, KM_SLEEP); acl->acl_flags = flags; return (acl); } void smb_fsacl_free(acl_t *acl) { if (acl) acl_free(acl); } /* * smb_fsop_aclmerge * * smb_fsop_aclread/write routines which interact with filesystem * work with single ACL. This routine merges given DACL and SACL * which might have been created during CIFS to FS conversion into * one single ACL. */ acl_t * smb_fsacl_merge(acl_t *dacl, acl_t *sacl) { acl_t *acl; int dacl_size; ASSERT(dacl); ASSERT(sacl); acl = smb_fsacl_alloc(dacl->acl_cnt + sacl->acl_cnt, dacl->acl_flags); dacl_size = dacl->acl_cnt * dacl->acl_entry_size; bcopy(dacl->acl_aclp, acl->acl_aclp, dacl_size); bcopy(sacl->acl_aclp, (char *)acl->acl_aclp + dacl_size, sacl->acl_cnt * sacl->acl_entry_size); return (acl); } /* * smb_fsacl_split * * splits the given ACE_T ACL (zacl) to one or two ACLs (DACL/SACL) based on * the 'which_acl' parameter. Note that output dacl/sacl parameters could be * NULL even if they're specified in 'which_acl', which means the target * doesn't have any access and/or audit ACEs. */ void smb_fsacl_split(acl_t *zacl, acl_t **dacl, acl_t **sacl, int which_acl) { ace_t *zace; ace_t *access_ace = NULL; ace_t *audit_ace = NULL; int naccess, naudit; int get_dacl, get_sacl; int i; *dacl = *sacl = NULL; naccess = naudit = 0; get_dacl = (which_acl & SMB_DACL_SECINFO); get_sacl = (which_acl & SMB_SACL_SECINFO); for (i = 0, zace = zacl->acl_aclp; i < zacl->acl_cnt; zace++, i++) { if (get_dacl && smb_ace_is_access(zace->a_type)) naccess++; else if (get_sacl && smb_ace_is_audit(zace->a_type)) naudit++; } if (naccess) { *dacl = smb_fsacl_alloc(naccess, zacl->acl_flags); access_ace = (*dacl)->acl_aclp; } if (naudit) { *sacl = smb_fsacl_alloc(naudit, zacl->acl_flags); audit_ace = (*sacl)->acl_aclp; } for (i = 0, zace = zacl->acl_aclp; i < zacl->acl_cnt; zace++, i++) { if (get_dacl && smb_ace_is_access(zace->a_type)) { *access_ace = *zace; access_ace++; } else if (get_sacl && smb_ace_is_audit(zace->a_type)) { *audit_ace = *zace; audit_ace++; } } } /* * ACE Inheritance Rules * * The system propagates inheritable ACEs to child objects according to a * set of inheritance rules. The system places inherited ACEs in the child's * DACL according to the preferred order of ACEs in a DACL. For Windows * 2000 or later, the system sets the INHERITED_ACE flag in all inherited ACEs. * * The following table shows the ACEs inherited by container and noncontainer * child objects for different combinations of inheritance flags. These * inheritance rules work the same for both DACLs and SACLs. * * Parent ACE type Effect on Child ACL * ----------------------- ------------------- * OBJECT_INHERIT_ACE only Noncontainer child objects: * Inherited as an effective ACE. * Container child objects: * Containers inherit an inherit-only ACE * unless the NO_PROPAGATE_INHERIT_ACE bit * flag is also set. * * CONTAINER_INHERIT_ACE only Noncontainer child objects: * No effect on the child object. * Container child objects: * The child object inherits an effective ACE. * The inherited ACE is inheritable unless the * NO_PROPAGATE_INHERIT_ACE bit flag is also set. * * CONTAINER_INHERIT_ACE and * OBJECT_INHERIT_ACE Noncontainer child objects: * Inherited as an effective ACE. * Container child objects: * The child object inherits an effective ACE. * The inherited ACE is inheritable unless the * NO_PROPAGATE_INHERIT_ACE bit flag is also set * * No inheritance flags set No effect on child container or noncontainer * objects. * * If an inherited ACE is an effective ACE for the child object, the system * maps any generic rights to the specific rights for the child object. * Similarly, the system maps generic SIDs, such as CREATOR_OWNER, to the * appropriate SID. If an inherited ACE is an inherit-only ACE, any generic * rights or generic SIDs are left unchanged so that they can be mapped * appropriately when the ACE is inherited by the next generation of child * objects. * * For a case in which a container object inherits an ACE that is both * effective on the container and inheritable by its descendants, the * container may inherit two ACEs. This occurs if the inheritable ACE * contains generic information. The container inherits an inherit-only * ACE containing the generic information and an effective-only ACE in * which the generic information has been mapped. */ #ifdef _KERNEL /* * smb_fsacl_inherit * * Manufacture the inherited ACL from the given ACL considering * the new object type (file/dir) specified by 'is_dir'. The * returned ACL is used in smb_fsop_create/smb_fsop_mkdir functions. * This function implements Windows inheritance rules explained above. * * Note that the in/out ACLs are ZFS ACLs not Windows ACLs */ acl_t * smb_fsacl_inherit(acl_t *dir_zacl, int is_dir, int which_acl, cred_t *cr) { boolean_t use_default = B_FALSE; int num_inheritable = 0; int numaces; ace_t *dir_zace; acl_t *new_zacl; ace_t *new_zace; ksid_t *owner_sid; ksid_t *group_sid; uid_t uid; gid_t gid; owner_sid = crgetsid(cr, KSID_OWNER); group_sid = crgetsid(cr, KSID_GROUP); ASSERT(owner_sid); ASSERT(group_sid); uid = owner_sid->ks_id; gid = group_sid->ks_id; num_inheritable = smb_fsacl_inheritable(dir_zacl, is_dir); if (num_inheritable == 0) { if (which_acl == SMB_DACL_SECINFO) { /* No inheritable access ACEs -> default DACL */ num_inheritable = DEFAULT_DACL_ACENUM; use_default = B_TRUE; } else { return (NULL); } } new_zacl = smb_fsacl_alloc(num_inheritable, ACL_AUTO_INHERIT); new_zace = new_zacl->acl_aclp; if (use_default) { bcopy(default_dacl, new_zacl->acl_aclp, sizeof (default_dacl)); new_zace->a_who = uid; return (new_zacl); } for (numaces = 0, dir_zace = dir_zacl->acl_aclp; numaces < dir_zacl->acl_cnt; dir_zace++, numaces++) { switch (dir_zace->a_flags & ACE_FD_INHERIT_ACE) { case (ACE_FILE_INHERIT_ACE | ACE_DIRECTORY_INHERIT_ACE): /* * Files inherit an effective ACE. * * Dirs inherit an effective ACE. * The inherited ACE is inheritable unless the * ACE_NO_PROPAGATE_INHERIT_ACE bit flag is also set */ smb_ace_inherit(dir_zace, new_zace, is_dir, uid, gid); new_zace++; if (is_dir && ZACE_IS_CREATOR(dir_zace) && (ZACE_IS_PROPAGATE(dir_zace))) { *new_zace = *dir_zace; new_zace->a_flags |= (ACE_INHERIT_ONLY_ACE | ACE_INHERITED_ACE); new_zace++; } break; case ACE_FILE_INHERIT_ACE: /* * Files inherit as an effective ACE. * * Dirs inherit an inherit-only ACE * unless the ACE_NO_PROPAGATE_INHERIT_ACE bit * flag is also set. */ if (is_dir == 0) { smb_ace_inherit(dir_zace, new_zace, is_dir, uid, gid); new_zace++; } else if (ZACE_IS_PROPAGATE(dir_zace)) { *new_zace = *dir_zace; new_zace->a_flags |= (ACE_INHERIT_ONLY_ACE | ACE_INHERITED_ACE); new_zace++; } break; case ACE_DIRECTORY_INHERIT_ACE: /* * No effect on files * * Dirs inherit an effective ACE. * The inherited ACE is inheritable unless the * ACE_NO_PROPAGATE_INHERIT_ACE bit flag is also set. */ if (is_dir == 0) break; smb_ace_inherit(dir_zace, new_zace, is_dir, uid, gid); new_zace++; if (ZACE_IS_CREATOR(dir_zace) && (ZACE_IS_PROPAGATE(dir_zace))) { *new_zace = *dir_zace; new_zace->a_flags |= (ACE_INHERIT_ONLY_ACE | ACE_INHERITED_ACE); new_zace++; } break; default: break; } } return (new_zacl); } #endif /* _KERNEL */ /* * smb_fsacl_from_vsa * * Converts given vsecattr_t structure to a acl_t structure. * * The allocated memory for retuned acl_t should be freed by * calling acl_free(). */ acl_t * smb_fsacl_from_vsa(vsecattr_t *vsecattr, acl_type_t acl_type) { int aclbsize = 0; /* size of acl list in bytes */ int dfaclbsize = 0; /* size of default acl list in bytes */ int numacls; acl_t *acl_info; ASSERT(vsecattr); acl_info = acl_alloc(acl_type); if (acl_info == NULL) return (NULL); acl_info->acl_flags = 0; switch (acl_type) { case ACLENT_T: numacls = vsecattr->vsa_aclcnt + vsecattr->vsa_dfaclcnt; aclbsize = vsecattr->vsa_aclcnt * sizeof (aclent_t); dfaclbsize = vsecattr->vsa_dfaclcnt * sizeof (aclent_t); acl_info->acl_cnt = numacls; acl_info->acl_aclp = kmem_alloc(aclbsize + dfaclbsize, KM_SLEEP); (void) memcpy(acl_info->acl_aclp, vsecattr->vsa_aclentp, aclbsize); (void) memcpy((char *)acl_info->acl_aclp + aclbsize, vsecattr->vsa_dfaclentp, dfaclbsize); if (acl_info->acl_cnt <= MIN_ACL_ENTRIES) acl_info->acl_flags |= ACL_IS_TRIVIAL; break; case ACE_T: aclbsize = vsecattr->vsa_aclcnt * sizeof (ace_t); acl_info->acl_cnt = vsecattr->vsa_aclcnt; acl_info->acl_flags = vsecattr->vsa_aclflags; acl_info->acl_aclp = kmem_alloc(aclbsize, KM_SLEEP); (void) memcpy(acl_info->acl_aclp, vsecattr->vsa_aclentp, aclbsize); if (ace_trivial(acl_info->acl_aclp, acl_info->acl_cnt) == 0) acl_info->acl_flags |= ACL_IS_TRIVIAL; break; default: acl_free(acl_info); return (NULL); } if (aclbsize && vsecattr->vsa_aclentp) kmem_free(vsecattr->vsa_aclentp, aclbsize); if (dfaclbsize && vsecattr->vsa_dfaclentp) kmem_free(vsecattr->vsa_dfaclentp, dfaclbsize); return (acl_info); } /* * smb_fsacl_to_vsa * * Converts given acl_t structure to a vsecattr_t structure. * * IMPORTANT: * Upon successful return the memory allocated for vsa_aclentp * should be freed by calling kmem_free(). The size is returned * in aclbsize. */ int smb_fsacl_to_vsa(acl_t *acl_info, vsecattr_t *vsecattr, int *aclbsize) { int error = 0; int numacls; aclent_t *aclp; ASSERT(acl_info); ASSERT(vsecattr); ASSERT(aclbsize); bzero(vsecattr, sizeof (vsecattr_t)); *aclbsize = 0; switch (acl_info->acl_type) { case ACLENT_T: numacls = acl_info->acl_cnt; /* * Minimum ACL size is three entries so might as well * bail out here. Also limit request size to prevent user * from allocating too much kernel memory. Maximum size * is MAX_ACL_ENTRIES for the ACL part and MAX_ACL_ENTRIES * for the default ACL part. */ if (numacls < 3 || numacls > (MAX_ACL_ENTRIES * 2)) { error = EINVAL; break; } vsecattr->vsa_mask = VSA_ACL; vsecattr->vsa_aclcnt = numacls; *aclbsize = numacls * sizeof (aclent_t); vsecattr->vsa_aclentp = kmem_alloc(*aclbsize, KM_SLEEP); (void) memcpy(vsecattr->vsa_aclentp, acl_info->acl_aclp, *aclbsize); /* Sort the acl list */ ksort((caddr_t)vsecattr->vsa_aclentp, vsecattr->vsa_aclcnt, sizeof (aclent_t), cmp2acls); /* Break into acl and default acl lists */ for (numacls = 0, aclp = vsecattr->vsa_aclentp; numacls < vsecattr->vsa_aclcnt; aclp++, numacls++) { if (aclp->a_type & ACL_DEFAULT) break; } /* Find where defaults start (if any) */ if (numacls < vsecattr->vsa_aclcnt) { vsecattr->vsa_mask |= VSA_DFACL; vsecattr->vsa_dfaclcnt = vsecattr->vsa_aclcnt - numacls; vsecattr->vsa_dfaclentp = aclp; vsecattr->vsa_aclcnt = numacls; } /* Adjust if they're all defaults */ if (vsecattr->vsa_aclcnt == 0) { vsecattr->vsa_mask &= ~VSA_ACL; vsecattr->vsa_aclentp = NULL; } /* Only directories can have defaults */ if (vsecattr->vsa_dfaclcnt && (acl_info->acl_flags & ACL_IS_DIR)) { error = ENOTDIR; } break; case ACE_T: if (acl_info->acl_cnt < 1 || acl_info->acl_cnt > MAX_ACL_ENTRIES) { error = EINVAL; break; } vsecattr->vsa_mask = VSA_ACE | VSA_ACE_ACLFLAGS; vsecattr->vsa_aclcnt = acl_info->acl_cnt; vsecattr->vsa_aclflags = acl_info->acl_flags & ACL_FLAGS_ALL; *aclbsize = vsecattr->vsa_aclcnt * sizeof (ace_t); vsecattr->vsa_aclentsz = *aclbsize; vsecattr->vsa_aclentp = kmem_alloc(*aclbsize, KM_SLEEP); (void) memcpy(vsecattr->vsa_aclentp, acl_info->acl_aclp, *aclbsize); break; default: error = EINVAL; } return (error); } #ifdef _KERNEL /* * smb_fsacl_inheritable * * Checks to see if there are any inheritable ACEs in the * given ZFS ACL. Returns the number of inheritable ACEs. * * The inherited ACL could be different based on the type of * new object (file/dir) specified by 'is_dir'. * * Note that the input ACL is a ZFS ACL not Windows ACL. */ static int smb_fsacl_inheritable(acl_t *zacl, int is_dir) { int numaces; int num_inheritable = 0; ace_t *zace; if (zacl == NULL) return (0); for (numaces = 0, zace = zacl->acl_aclp; numaces < zacl->acl_cnt; zace++, numaces++) { switch (zace->a_flags & ACE_FD_INHERIT_ACE) { case (ACE_FILE_INHERIT_ACE | ACE_DIRECTORY_INHERIT_ACE): /* * Files inherit an effective ACE. * * Dirs inherit an effective ACE. * The inherited ACE is inheritable unless the * ACE_NO_PROPAGATE_INHERIT_ACE bit flag is also set */ num_inheritable++; if (is_dir && ZACE_IS_CREATOR(zace) && (ZACE_IS_PROPAGATE(zace))) { num_inheritable++; } break; case ACE_FILE_INHERIT_ACE: /* * Files inherit as an effective ACE. * * Dirs inherit an inherit-only ACE * unless the ACE_NO_PROPAGATE_INHERIT_ACE bit * flag is also set. */ if (is_dir == 0) num_inheritable++; else if (ZACE_IS_PROPAGATE(zace)) num_inheritable++; break; case ACE_DIRECTORY_INHERIT_ACE: /* * No effect on files * * Dirs inherit an effective ACE. * The inherited ACE is inheritable unless the * ACE_NO_PROPAGATE_INHERIT_ACE bit flag is also set. */ if (is_dir == 0) break; num_inheritable++; if (ZACE_IS_CREATOR(zace) && (ZACE_IS_PROPAGATE(zace))) num_inheritable++; break; default: break; } } return (num_inheritable); } #endif /* _KERNEL */ /* * ACE Functions */ /* * This is generic (ACL version 2) vs. object-specific * (ACL version 4) ACE types. */ boolean_t smb_ace_is_generic(int type) { switch (type) { case ACE_ACCESS_ALLOWED_ACE_TYPE: case ACE_ACCESS_DENIED_ACE_TYPE: case ACE_SYSTEM_AUDIT_ACE_TYPE: case ACE_SYSTEM_ALARM_ACE_TYPE: case ACE_ACCESS_ALLOWED_CALLBACK_ACE_TYPE: case ACE_ACCESS_DENIED_CALLBACK_ACE_TYPE: case ACE_SYSTEM_AUDIT_CALLBACK_ACE_TYPE: case ACE_SYSTEM_ALARM_CALLBACK_ACE_TYPE: return (B_TRUE); default: break; } return (B_FALSE); } boolean_t smb_ace_is_access(int type) { switch (type) { case ACE_ACCESS_ALLOWED_ACE_TYPE: case ACE_ACCESS_DENIED_ACE_TYPE: case ACE_ACCESS_ALLOWED_COMPOUND_ACE_TYPE: case ACE_ACCESS_ALLOWED_OBJECT_ACE_TYPE: case ACE_ACCESS_DENIED_OBJECT_ACE_TYPE: case ACE_ACCESS_ALLOWED_CALLBACK_ACE_TYPE: case ACE_ACCESS_DENIED_CALLBACK_ACE_TYPE: case ACE_ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE: case ACE_ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE: return (B_TRUE); default: break; } return (B_FALSE); } boolean_t smb_ace_is_audit(int type) { switch (type) { case ACE_SYSTEM_AUDIT_ACE_TYPE: case ACE_SYSTEM_AUDIT_OBJECT_ACE_TYPE: case ACE_SYSTEM_AUDIT_CALLBACK_ACE_TYPE: case ACE_SYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPE: return (B_TRUE); default: break; } return (B_FALSE); } /* * smb_ace_len * * Returns the length of the given ACE as it appears in an * ACL on the wire (i.e. a flat buffer which contains the SID) */ static uint16_t smb_ace_len(smb_ace_t *ace) { ASSERT(ace); ASSERT(ace->se_sid); if (ace == NULL) return (0); return (SMB_ACE_HDRSIZE + sizeof (ace->se_mask) + smb_sid_len(ace->se_sid)); } #ifdef _KERNEL static void smb_ace_inherit(ace_t *dir_zace, ace_t *zace, int is_dir, uid_t uid, gid_t gid) { *zace = *dir_zace; /* This is an effective ACE so remove the inherit_only flag */ zace->a_flags &= ~ACE_INHERIT_ONLY_ACE; /* Mark this ACE as inherited */ zace->a_flags |= ACE_INHERITED_ACE; /* * If this is a file or NO_PROPAGATE is set then this inherited * ACE is not inheritable so clear the inheritance flags */ if (!(is_dir && ZACE_IS_PROPAGATE(dir_zace))) zace->a_flags &= ~ACE_INHERIT_FLAGS; /* * Replace creator owner/group ACEs with actual owner/group ACEs. * This is a non-inheritable effective ACE. */ if (ZACE_IS_CREATOR_OWNER(dir_zace)) { zace->a_who = uid; zace->a_flags &= ~ACE_INHERIT_FLAGS; } else if (ZACE_IS_CREATOR_GROUP(dir_zace)) { zace->a_who = gid; zace->a_flags |= ACE_IDENTIFIER_GROUP; zace->a_flags &= ~ACE_INHERIT_FLAGS; } } #endif /* _KERNEL */ /* * smb_ace_mask_g2s * * Converts generic access bits in the given mask (if any) * to file specific bits. Generic access masks shouldn't be * stored in filesystem ACEs. */ static uint32_t smb_ace_mask_g2s(uint32_t mask) { if (mask & GENERIC_ALL) { mask &= ~(GENERIC_ALL | GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE); mask |= FILE_ALL_ACCESS; return (mask); } if (mask & GENERIC_READ) { mask &= ~GENERIC_READ; mask |= FILE_GENERIC_READ; } if (mask & GENERIC_WRITE) { mask &= ~GENERIC_WRITE; mask |= FILE_GENERIC_WRITE; } if (mask & GENERIC_EXECUTE) { mask &= ~GENERIC_EXECUTE; mask |= FILE_GENERIC_EXECUTE; } return (mask); } /* * smb_ace_flags_tozfs * * This function maps the flags which have different values * in Windows and Solaris. The ones with the same value are * transferred untouched. */ static uint16_t smb_ace_flags_tozfs(uint8_t c_flags) { uint16_t z_flags = 0; if (c_flags & SUCCESSFUL_ACCESS_ACE_FLAG) z_flags |= ACE_SUCCESSFUL_ACCESS_ACE_FLAG; if (c_flags & FAILED_ACCESS_ACE_FLAG) z_flags |= ACE_FAILED_ACCESS_ACE_FLAG; if (c_flags & INHERITED_ACE) z_flags |= ACE_INHERITED_ACE; z_flags |= (c_flags & ACE_INHERIT_FLAGS); return (z_flags); } static uint8_t smb_ace_flags_fromzfs(uint16_t z_flags) { uint8_t c_flags; c_flags = z_flags & ACE_INHERIT_FLAGS; if (z_flags & ACE_SUCCESSFUL_ACCESS_ACE_FLAG) c_flags |= SUCCESSFUL_ACCESS_ACE_FLAG; if (z_flags & ACE_FAILED_ACCESS_ACE_FLAG) c_flags |= FAILED_ACCESS_ACE_FLAG; if (z_flags & ACE_INHERITED_ACE) c_flags |= INHERITED_ACE; return (c_flags); } static boolean_t smb_ace_isvalid(smb_ace_t *ace, int which_acl) { uint16_t min_len; min_len = sizeof (smb_acehdr_t); if (ace->se_hdr.se_bsize < min_len) return (B_FALSE); if (smb_ace_is_access(ace->se_hdr.se_type) && (which_acl != SMB_DACL_SECINFO)) return (B_FALSE); if (smb_ace_is_audit(ace->se_hdr.se_type) && (which_acl != SMB_SACL_SECINFO)) return (B_FALSE); if (smb_ace_is_generic(ace->se_hdr.se_type)) { if (!smb_sid_isvalid(ace->se_sid)) return (B_FALSE); min_len += sizeof (ace->se_mask); min_len += smb_sid_len(ace->se_sid); if (ace->se_hdr.se_bsize < min_len) return (B_FALSE); } /* * object-specific ACE validation will be added later. */ return (B_TRUE); }