summaryrefslogtreecommitdiff
path: root/usr/src/uts/common/fs/smbsrv/smb_acl.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/uts/common/fs/smbsrv/smb_acl.c')
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_acl.c1310
1 files changed, 1310 insertions, 0 deletions
diff --git a/usr/src/uts/common/fs/smbsrv/smb_acl.c b/usr/src/uts/common/fs/smbsrv/smb_acl.c
new file mode 100644
index 0000000000..2617268d9e
--- /dev/null
+++ b/usr/src/uts/common/fs/smbsrv/smb_acl.c
@@ -0,0 +1,1310 @@
+/*
+ * 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 2007 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * Platform SDK: Security
+ *
+ * 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.
+ */
+
+#include <sys/acl.h>
+#include <smbsrv/smb_incl.h>
+#include <smbsrv/ntsid.h>
+#include <smbsrv/smb_fsops.h>
+#include <smbsrv/smb_idmap.h>
+
+#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))
+
+static int smb_ace_isvalid(smb_ace_hdr_t *ace, int which_acl);
+static int smb_ace_append_generic(smb_acl_t *acl, void *generic_ace);
+
+static int smb_ace_common_add(
+ smb_acl_t *acl,
+ uint8_t type,
+ uint8_t flags,
+ uint32_t access_mask,
+ nt_sid_t *sid);
+
+static void smb_ace_inherit(ace_t *dir_zace, ace_t *zace, int is_dir);
+static uint16_t smb_ace_flags_tozfs(uint8_t c_flags, int isdir);
+static uint8_t smb_ace_flags_fromzfs(uint16_t z_flags);
+static void smb_acl_init(smb_acl_t *acl, uint16_t size, uint8_t rev);
+
+static int
+smb_ace_isvalid(smb_ace_hdr_t *ace, int which_acl)
+{
+ uint16_t min_len;
+ smb_ace_t *p;
+
+ min_len = sizeof (smb_ace_hdr_t);
+
+ if (ace->se_size < min_len)
+ return (0);
+
+ if (smb_ace_is_access(ace->se_type) &&
+ (which_acl != SMB_DACL_SECINFO)) {
+ return (0);
+ }
+
+ if (smb_ace_is_audit(ace->se_type) &&
+ (which_acl != SMB_SACL_SECINFO)) {
+ return (0);
+ }
+
+ if (smb_ace_is_generic(ace->se_type)) {
+ /*LINTED E_BAD_PTR_CAST_ALIGN*/
+ p = (smb_ace_t *)ace;
+
+ if (ace->se_size < sizeof (*p))
+ return (0); /* won't handle empty SubAuthority[] */
+
+ if (nt_sid_is_valid(&p->se_sid) == 0)
+ return (0);
+
+ min_len += sizeof (p->se_mask);
+ min_len += nt_sid_length(&p->se_sid);
+
+ if (ace->se_size < min_len)
+ return (0);
+ }
+
+ /*
+ * XXX object-specific ACE validation will be added later.
+ */
+ return (1);
+}
+
+int
+smb_acl_isvalid(smb_acl_t *acl, int which_acl)
+{
+ uint16_t min_len;
+ unsigned char *scan;
+ unsigned char *scan_end;
+ smb_ace_hdr_t *ace;
+ uint16_t count = 0;
+
+ min_len = sizeof (smb_acl_t);
+
+ if (acl->sl_size < min_len)
+ return (0);
+
+ if (acl->sl_revision != ACL_REVISION) {
+ /*
+ * XXX we are rejecting ACLs with object-specific ACEs for now
+ */
+ return (0);
+ }
+
+ scan = (unsigned char *) &acl[0];
+ scan_end = scan + acl->sl_size;
+ scan = (unsigned char *) &acl[1]; /* skip Acl header */
+
+ while (count < acl->sl_acecnt && scan < scan_end) {
+ /*LINTED E_BAD_PTR_CAST_ALIGN*/
+ ace = (smb_ace_hdr_t *)scan;
+
+ if (scan + sizeof (smb_ace_hdr_t) >= scan_end)
+ return (0);
+
+ if (scan + ace->se_size > scan_end)
+ return (0); /* overflow */
+
+ if (!smb_ace_isvalid(ace, which_acl))
+ return (0);
+
+ scan += ace->se_size;
+ count++;
+ }
+
+ return (1);
+}
+
+
+static void
+smb_acl_init(smb_acl_t *acl, uint16_t size, uint8_t rev)
+{
+ bzero(acl, size);
+ acl->sl_revision = rev;
+ acl->sl_size = size;
+}
+
+uint16_t
+smb_acl_len(smb_acl_t *acl)
+{
+ smb_ace_hdr_t *ace;
+ unsigned char *scan_beg;
+ unsigned char *scan_end;
+ unsigned char *scan;
+ uint16_t length;
+ uint16_t count;
+
+ scan_beg = (unsigned char *) &acl[0];
+ scan_end = scan_beg + acl->sl_size;
+ scan = (unsigned char *) &acl[1];
+ length = sizeof (smb_acl_t);
+ count = 0;
+
+ while ((count < acl->sl_acecnt) && (scan < scan_end)) {
+ /*LINTED E_BAD_PTR_CAST_ALIGN*/
+ ace = (smb_ace_hdr_t *)scan;
+ length += ace->se_size;
+ scan += ace->se_size;
+ count++;
+ }
+
+ return (length);
+}
+
+/*
+ * Append the generic ACE to the ACL. This is used to put any
+ * kind of ACE on the ACL so the argument is declared as a void*. We cast it
+ * to an ACCESS_ALLOWED_ACE just because there is no sense of a generic ACE.
+ */
+static int
+smb_ace_append_generic(smb_acl_t *acl, void *generic_ace)
+{
+ smb_ace_t *ace = (smb_ace_t *)generic_ace;
+ uint16_t acl_len = smb_acl_len(acl);
+ unsigned char *scan = (uchar_t *)acl;
+
+ if ((acl_len + ace->se_header.se_size) > acl->sl_size) {
+ /* no room in the acl for this ace */
+ return (0);
+ }
+
+ /* append the ace to the acl and inc ace count */
+ bcopy(ace, &scan[acl_len], ace->se_header.se_size);
+ acl->sl_acecnt++;
+
+ return (1);
+}
+
+/*
+ * Helper for the ACL sort routine
+ */
+typedef struct smb_ace_entry {
+ smb_ace_t *e_ace;
+ list_node_t e_node;
+} smb_ace_entry_t;
+
+/*
+ * 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
+
+/*
+ * smb_acl_do_sort
+ *
+ * Sorts the given ACL, acl, and returns the result
+ * in a newly allocated memory.
+ *
+ * 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
+ *
+ * Of course, not all ACE types are required in an ACL.
+ */
+static smb_acl_t *
+smb_acl_do_sort(smb_acl_t *acl, list_t *ace_grps)
+{
+ smb_acl_t *sorted_acl;
+ smb_ace_entry_t *nae;
+ int i;
+
+ sorted_acl = kmem_alloc(acl->sl_size, KM_SLEEP);
+ *sorted_acl = *acl;
+
+ /* start with no ACE in the sorted ACL */
+ sorted_acl->sl_acecnt = 0;
+
+ /*
+ * start with highest priority ACE group and append
+ * the ACEs to the ACL.
+ */
+ for (i = SMB_AG_NUM - 1; i >= SMB_AG_START; i--) {
+ nae = list_head(&ace_grps[i]);
+ while (nae) {
+ if (!smb_ace_append_generic(sorted_acl, nae->e_ace)) {
+ kmem_free(sorted_acl, acl->sl_size);
+ return (NULL);
+ }
+ nae = list_next(&ace_grps[i], nae);
+ }
+ }
+
+ return (sorted_acl);
+}
+
+/*
+ * smb_acl_need_sort
+ *
+ * Here is the desired ACE order
+ *
+ * deny-direct, allow-direct, deny-inherited, allow-inherited
+ *
+ * If any ace has been encountered which belongs to a group
+ * with lower priority of the specified ace_grp then the acl
+ * should be sorted.
+ */
+static int
+smb_acl_need_sort(list_t *ace_grps, int ace_grp)
+{
+ int i;
+
+ for (i = SMB_AG_START; i < ace_grp; i++)
+ if (!list_is_empty(&ace_grps[i]))
+ return (1);
+
+ return (0);
+}
+
+/*
+ * smb_acl_sort
+ *
+ * Returns NULL upon failure.
+ * Returns pointer to the passed (original) acl if no sort is required.
+ * Returns pointer to a new acl upon successful sort in which case the
+ * caller is responsible for freeing the allocated memory.
+ */
+smb_acl_t *
+smb_acl_sort(smb_acl_t *acl)
+{
+ smb_acl_t *sorted_acl;
+ smb_ace_t *ace;
+ smb_ace_entry_t *ace_list;
+ int ace_list_size;
+ list_t ace_grps[SMB_AG_NUM];
+ int ag;
+ int do_sort = 0;
+ uint16_t i;
+ uint8_t ace_flags;
+
+ ASSERT(acl);
+
+ if (acl->sl_acecnt == 0) {
+ /*
+ * ACL with no entry is a valid ACL and it means
+ * no access for anybody.
+ */
+ return (acl);
+ }
+
+ for (i = SMB_AG_START; i < SMB_AG_NUM; i++) {
+ list_create(&ace_grps[i], sizeof (smb_ace_entry_t),
+ offsetof(smb_ace_entry_t, e_node));
+ }
+
+ /*
+ * Allocate the helper entries to group the ACEs based on
+ * the desired priorities.
+ */
+ ace_list_size = sizeof (smb_ace_entry_t) * acl->sl_acecnt;
+ ace_list = kmem_alloc(ace_list_size, KM_SLEEP);
+
+ for (i = 0; i < acl->sl_acecnt; ++i) {
+ ace_list[i].e_ace = smb_ace_get(acl, i);
+ ace = ace_list[i].e_ace;
+ ASSERT(ace);
+
+ ace_flags = ace->se_header.se_flags;
+
+ switch (ace->se_header.se_type) {
+ case ACCESS_DENIED_ACE_TYPE:
+ if (ace_flags & INHERITED_ACE) {
+ ag = SMB_AG_DNY_INHRT;
+ do_sort |= smb_acl_need_sort(ace_grps, ag);
+ } else {
+ ag = SMB_AG_DNY_DRCT;
+ do_sort |= smb_acl_need_sort(ace_grps, ag);
+ }
+ break;
+
+ case ACCESS_ALLOWED_ACE_TYPE:
+ if (ace_flags & INHERITED_ACE) {
+ ag = SMB_AG_ALW_INHRT;
+ } else {
+ ag = SMB_AG_ALW_DRCT;
+ do_sort |= smb_acl_need_sort(ace_grps, ag);
+ }
+ break;
+
+ default:
+ /*
+ * This is the lowest priority group so we put
+ * evertything unknown here.
+ */
+ ag = SMB_AG_ALW_INHRT;
+ break;
+ }
+
+ /* Put the element on the appropriate list */
+ list_insert_tail(&ace_grps[ag], &ace_list[i]);
+ }
+
+ if (do_sort)
+ sorted_acl = smb_acl_do_sort(acl, ace_grps);
+ else
+ sorted_acl = acl;
+
+ for (i = SMB_AG_START; i < SMB_AG_NUM; i++) {
+ void *ent;
+ list_t *alist = &ace_grps[i];
+
+ while ((ent = list_head(alist)) != NULL)
+ list_remove(alist, ent);
+ list_destroy(alist);
+ }
+
+ kmem_free(ace_list, ace_list_size);
+
+ return (sorted_acl);
+}
+
+static int
+smb_ace_common_add(
+ smb_acl_t *acl,
+ uint8_t type,
+ uint8_t flags,
+ uint32_t access_mask,
+ nt_sid_t *sid)
+{
+ smb_ace_t *ace;
+ unsigned char *scan = (unsigned char *) acl;
+ uint16_t used = smb_acl_len(acl);
+ uint16_t sid_len = nt_sid_length(sid);
+ uint16_t size;
+
+ size = sizeof (ace->se_header) + sizeof (ace->se_mask) + sid_len;
+
+ if (size + used > acl->sl_size) {
+ /* won't fit */
+ return (0);
+ }
+
+ /*LINTED E_BAD_PTR_CAST_ALIGN*/
+ ace = (smb_ace_t *)&scan[used];
+
+ ace->se_header.se_type = type;
+ ace->se_header.se_flags = flags;
+ ace->se_header.se_size = size;
+ ace->se_mask = access_mask;
+ bcopy(sid, &ace->se_sid, sid_len);
+
+ acl->sl_acecnt++;
+
+ return (1);
+}
+
+smb_ace_t *
+smb_ace_get(smb_acl_t *acl, uint16_t idx)
+{
+ smb_ace_t *ace;
+ unsigned char *scan_beg = (unsigned char *) &acl[0];
+ unsigned char *scan_end = scan_beg + acl->sl_size;
+ unsigned char *scan = (unsigned char *) &acl[1];
+ uint16_t count = 0;
+
+ if (idx >= acl->sl_acecnt)
+ return (NULL);
+
+ while (count <= idx && scan < scan_end) {
+ /*LINTED E_BAD_PTR_CAST_ALIGN*/
+ ace = (smb_ace_t *)scan;
+
+ if (count == idx) {
+ return (ace);
+ }
+
+ scan += ace->se_header.se_size;
+ count++;
+ }
+
+ return (NULL);
+}
+
+int
+smb_acl_copy(uint16_t buflen, smb_acl_t *dst_acl, smb_acl_t *src_acl)
+{
+ smb_ace_hdr_t *dst_ace;
+ smb_ace_hdr_t *src_ace;
+ unsigned char *scan = (unsigned char *) &src_acl[1];
+ unsigned char *dest_beg = (unsigned char *) &dst_acl[0];
+ unsigned char *dest_end;
+ unsigned char *dest = (unsigned char *) &dst_acl[1];
+ uint16_t count = 0;
+ uint16_t n_bytes;
+
+ n_bytes = smb_acl_len(src_acl);
+ if (n_bytes > buflen)
+ return (0);
+
+ dest_end = dest_beg + n_bytes;
+
+ dst_acl->sl_revision = src_acl->sl_revision;
+ dst_acl->sl_sbz1 = 0;
+ dst_acl->sl_size = n_bytes;
+ dst_acl->sl_acecnt = 0;
+ dst_acl->sl_sbz2 = 0;
+
+ while (count < src_acl->sl_acecnt && dest < dest_end) {
+ /*LINTED E_BAD_PTR_CAST_ALIGN*/
+ src_ace = (smb_ace_hdr_t *)scan;
+ /*LINTED E_BAD_PTR_CAST_ALIGN*/
+ dst_ace = (smb_ace_hdr_t *)dest;
+ bcopy(src_ace, dst_ace, src_ace->se_size);
+ dest += dst_ace->se_size;
+ dst_acl->sl_acecnt++;
+ scan += src_ace->se_size;
+ count++;
+ }
+
+ /*LINTED E_PTRDIFF_OVERFLOW*/
+ return (dest - dest_beg);
+}
+
+/*
+ * smb_ace_len
+ *
+ * Returns the length of an ACE with the given SID
+ *
+ * struct smb_ace {
+ * smb_ace_hdr_t se_header;
+ * uint32_t se_mask;
+ * nt_sid_t se_sid;
+ * };
+ */
+uint16_t
+smb_ace_len(nt_sid_t *sid)
+{
+ ASSERT(sid);
+
+ return (sizeof (smb_ace_hdr_t)
+ + sizeof (uint32_t) + nt_sid_length(sid));
+}
+
+/*
+ * 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.
+ */
+uint32_t
+smb_ace_mask_g2s(DWORD 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_acl_getsids
+ *
+ * Batch all the uid/gid in given ZFS ACL to get their corresponding SIDs.
+ */
+static idmap_stat
+smb_acl_getsids(smb_idmap_batch_t *sib, acl_t *zacl, uid_t uid, gid_t gid)
+{
+ 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++) {
+ switch (zace->a_flags & ACE_TYPE_FLAGS) {
+ case ACE_OWNER:
+ id = uid;
+ idtype = SMB_IDMAP_USER;
+ break;
+
+ case (ACE_GROUP | ACE_IDENTIFIER_GROUP):
+ /* owning group */
+ id = gid;
+ idtype = SMB_IDMAP_GROUP;
+ break;
+
+ case ACE_IDENTIFIER_GROUP:
+ /* regular group */
+ id = zace->a_who;
+ idtype = SMB_IDMAP_GROUP;
+ break;
+
+ case ACE_EVERYONE:
+ idtype = SMB_IDMAP_EVERYONE;
+ break;
+
+ default:
+ /* user entry */
+ id = zace->a_who;
+ idtype = SMB_IDMAP_USER;
+ }
+
+ 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);
+ return (idm_stat);
+}
+
+/*
+ * smb_acl_grow
+ *
+ * Grow the acl size by given number of bytes in 'grow'
+ * Returns pointer to the newly allocated memory.
+ */
+static smb_acl_t *
+smb_acl_grow(smb_acl_t *acl, uint16_t grow)
+{
+ smb_acl_t *new_acl;
+ uint16_t smb_aclsz;
+
+ ASSERT(acl);
+
+ smb_aclsz = acl->sl_size;
+ new_acl = kmem_alloc(smb_aclsz + grow, KM_SLEEP);
+ (void) memcpy(new_acl, acl, smb_aclsz);
+ kmem_free(acl, smb_aclsz);
+ new_acl->sl_size = smb_aclsz + grow;
+
+ return (new_acl);
+}
+
+/*
+ * 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, uid_t uid, gid_t gid)
+{
+ ace_t *zace;
+ int numaces;
+ smb_acl_t *acl;
+ uint16_t smb_aclsz;
+ smb_idmap_batch_t sib;
+ smb_idmap_t *sim;
+ idmap_stat idm_stat;
+ int status;
+
+ idm_stat = smb_idmap_batch_create(&sib, zacl->acl_cnt,
+ SMB_IDMAP_ID2SID);
+ if (idm_stat != IDMAP_SUCCESS)
+ return (NULL);
+
+ if (smb_acl_getsids(&sib, zacl, uid, gid) != IDMAP_SUCCESS) {
+ smb_idmap_batch_destroy(&sib);
+ return (NULL);
+ }
+
+ smb_aclsz = sizeof (smb_acl_t);
+
+ acl = kmem_alloc(smb_aclsz, KM_SLEEP);
+ smb_acl_init(acl, smb_aclsz, ACL_REVISION);
+
+ 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) {
+ kmem_free(acl, acl->sl_size);
+ acl = NULL;
+ break;
+ }
+
+ /* Make room for this ACE */
+ acl = smb_acl_grow(acl, smb_ace_len(sim->sim_sid));
+
+ status = smb_ace_common_add(acl,
+ zace->a_type,
+ smb_ace_flags_fromzfs(zace->a_flags),
+ zace->a_access_mask,
+ sim->sim_sid);
+
+ if (status == 0) {
+ kmem_free(acl, acl->sl_size);
+ acl = NULL;
+ break;
+ }
+ }
+
+ smb_idmap_batch_destroy(&sib);
+ return (acl);
+}
+
+/*
+ * SID for Everyone group: S-1-1-0.
+ */
+nt_sid_t everyone_sid = {
+ NT_SID_REVISION,
+ 1,
+ NT_SECURITY_WORLD_AUTH,
+ { 0 }
+};
+
+/*
+ * smb_acl_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.
+ */
+acl_t *
+smb_acl_null_empty(int null)
+{
+ acl_t *zacl;
+ ace_t *zace;
+
+ zacl = smb_fsop_aclalloc(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);
+}
+
+/*
+ * 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_fsop_aclfree().
+ *
+ * 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;
+ int i, isdir;
+
+ 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_acl_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);
+
+ isdir = ((flags & ACL_IS_DIR) == ACL_IS_DIR);
+
+ zacl = smb_fsop_aclalloc(acl->sl_acecnt, flags);
+
+ zace = zacl->acl_aclp;
+ sim = sib.sib_maps;
+
+ for (i = 0; ace = smb_ace_get(acl, i); i++, zace++, sim++) {
+ zace->a_type = ace->se_header.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_header.se_flags,
+ isdir);
+
+ if (nt_sid_is_equal(&ace->se_sid, &everyone_sid))
+ zace->a_flags |= ACE_EVERYONE;
+ else {
+ sim->sim_id = &zace->a_who;
+ idm_stat = smb_idmap_batch_getid(sib.sib_idmaph, sim,
+ &ace->se_sid, -1);
+
+ if (idm_stat != IDMAP_SUCCESS) {
+ smb_fsop_aclfree(zacl);
+ smb_idmap_batch_destroy(&sib);
+ return (NT_STATUS_INTERNAL_ERROR);
+ }
+ }
+ }
+
+ idm_stat = smb_idmap_batch_getmappings(&sib);
+ if (idm_stat != IDMAP_SUCCESS) {
+ smb_fsop_aclfree(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;
+ sim = sib.sib_maps;
+ for (i = 0; i < acl->sl_acecnt; i++, zace++, sim++) {
+ if (zace->a_flags & ACE_EVERYONE)
+ 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);
+}
+
+/*
+ * smb_acl_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.
+ *
+ * Any ACE except creator owner/group:
+ *
+ * FI DI NP #F #D
+ * ---- ---- ---- ---- ----
+ * - - ? 0 0
+ * X - - 1 1
+ * X - X 1 0
+ * - X - 0 1
+ * - X X 0 1
+ * X X - 1 1
+ * X X X 1 1
+ *
+ * Creator owner/group ACE:
+ *
+ * FI DI NP #F #D
+ * ---- ---- ---- ---- ----
+ * - - ? 0 0
+ * X - - 1r 1c
+ * X - X 1r 0
+ * - X - 0 2
+ * - X X 0 1r
+ * X X - 1r 2
+ * X X X 1r 1r
+ *
+ * Legend:
+ *
+ * FI: File Inherit
+ * DI: Dir Inherit
+ * NP: No Propagate
+ * #F: #ACE for a new file
+ * #D: #ACE for a new dir
+ *
+ * X: bit is set
+ * -: bit is not set
+ * ?: don't care
+ *
+ * 1r: one owner/group ACE
+ * 1c: one creator owner/group ACE
+ */
+static int
+smb_acl_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);
+}
+
+#define DEFAULT_DACL_ACENUM 2
+/*
+ * Default ACL:
+ * owner: full access
+ * SYSTEM: full access
+ */
+static 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 }
+};
+
+/*
+ * smb_acl_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.
+ *
+ * Note that the in/our ACLs are ZFS ACLs not Windows ACLs
+ */
+acl_t *
+smb_acl_inherit(acl_t *dir_zacl, int is_dir, int which_acl, uid_t owner_uid)
+{
+ boolean_t use_default = B_FALSE;
+ int num_inheritable = 0;
+ int numaces;
+ ace_t *dir_zace;
+ acl_t *new_zacl;
+ ace_t *new_zace;
+
+ num_inheritable = smb_acl_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_fsop_aclalloc(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 = owner_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);
+ 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);
+ 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);
+ 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);
+}
+
+static void
+smb_ace_inherit(ace_t *dir_zace, ace_t *zace, int is_dir)
+{
+ *zace = *dir_zace;
+ if (!(is_dir && ZACE_IS_PROPAGATE(dir_zace)))
+ zace->a_flags &= ~ACE_INHERIT_FLAGS;
+ zace->a_flags |= ACE_INHERITED_ACE;
+
+ /*
+ * Replace creator owner/group ACEs with
+ * actual owner/group ACEs.
+ */
+ if (ZACE_IS_CREATOR_OWNER(dir_zace)) {
+ zace->a_who = (uid_t)-1;
+ zace->a_flags |= ACE_OWNER;
+ } else if (ZACE_IS_CREATOR_GROUP(dir_zace)) {
+ zace->a_who = (uid_t)-1;
+ zace->a_flags |= ACE_GROUP;
+ }
+}
+
+static uint16_t
+smb_ace_flags_tozfs(uint8_t c_flags, int isdir)
+{
+ 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;
+
+ /*
+ * ZFS doesn't like any inheritance flags to be set on a
+ * file's ACE, only directories. Windows doesn't care.
+ */
+ if (isdir)
+ 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);
+}
+
+/*
+ * This is generic (ACL version 2) vs. object-specific
+ * (ACL version 4) ACE types.
+ */
+int
+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 (1);
+
+ default:
+ break;
+ }
+
+ return (0);
+}
+
+int
+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 (1);
+
+ default:
+ break;
+ }
+
+ return (0);
+}
+
+int
+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 (1);
+
+ default:
+ break;
+ }
+
+ return (0);
+}