root/usr/src/lib/lib9p/common/genacl.c
/*
 * Copyright 2016 Chris Torek <torek@ixsystems.com>
 * All rights reserved
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted providing that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/acl.h>
#include <sys/stat.h>

#ifdef __illumos__
#include <sys/sysmacros.h>
#endif

#include "lib9p.h"
#include "lib9p_impl.h"
#include "genacl.h"
#include "fid.h"
#include "log.h"

#ifndef __illumos__
typedef int econvertfn(acl_entry_t, struct l9p_ace *);
#endif

#ifdef __FreeBSD__
static struct l9p_acl *l9p_new_acl(uint32_t acetype, uint32_t aceasize);
static struct l9p_acl *l9p_growacl(struct l9p_acl *acl, uint32_t aceasize);
static int l9p_count_aces(acl_t sysacl);
static struct l9p_acl *l9p_sysacl_to_acl(int, acl_t, econvertfn *);
#endif
static bool l9p_ingroup(gid_t tid, gid_t gid, gid_t *gids, size_t ngids);
static int l9p_check_aces(int32_t mask, struct l9p_acl *acl, struct stat *st,
    uid_t uid, gid_t gid, gid_t *gids, size_t ngids);

void
l9p_acl_free(struct l9p_acl *acl)
{

        free(acl);
}

/*
 * Is the given group ID tid (test-id) any of the gid's in agids?
 */
static bool
l9p_ingroup(gid_t tid, gid_t gid, gid_t *gids, size_t ngids)
{
        size_t i;

        if (tid == gid)
                return (true);
        for (i = 0; i < ngids; i++)
                if (tid == gids[i])
                        return (true);
        return (false);
}

/* #define ACE_DEBUG */

/*
 * Note that NFSv4 tests are done on a "first match" basis.
 * That is, we check each ACE sequentially until we run out
 * of ACEs, or find something explicitly denied (DENIED!),
 * or have cleared out all our attempt-something bits.  Once
 * we come across an ALLOW entry for the bits we're trying,
 * we clear those from the bits we're still looking for, in
 * the order they appear.
 *
 * The result is either "definitely allowed" (we cleared
 * all the bits), "definitely denied" (we hit a deny with
 * some or all of the bits), or "unspecified".  We
 * represent these three states as +1 (positive = yes = allow),
 * -1 (negative = no = denied), or 0 (no strong answer).
 *
 * For our caller's convenience, if we are called with a
 * mask of 0, we return 0 (no answer).
 */
static int
l9p_check_aces(int32_t mask, struct l9p_acl *acl, struct stat *st,
    uid_t uid, gid_t gid, gid_t *gids, size_t ngids)
{
        uint32_t i;
        struct l9p_ace *ace;
#ifdef ACE_DEBUG
        const char *acetype, *allowdeny;
        bool show_tid;
#endif
        bool match;
        uid_t tid;

        if (mask == 0)
                return (0);

        for (i = 0; mask != 0 && i < acl->acl_nace; i++) {
                ace = &acl->acl_aces[i];
                switch (ace->ace_type) {
                case L9P_ACET_ACCESS_ALLOWED:
                case L9P_ACET_ACCESS_DENIED:
                        break;
                default:
                        /* audit, alarm - ignore */
                        continue;
                }
#ifdef ACE_DEBUG
                show_tid = false;
#endif
                if (ace->ace_flags & L9P_ACEF_OWNER) {
#ifdef ACE_DEBUG
                        acetype = "OWNER@";
#endif
                        match = st->st_uid == uid;
                } else if (ace->ace_flags & L9P_ACEF_GROUP) {
#ifdef ACE_DEBUG
                        acetype = "GROUP@";
#endif
                        match = l9p_ingroup(st->st_gid, gid, gids, ngids);
                } else if (ace->ace_flags & L9P_ACEF_EVERYONE) {
#ifdef ACE_DEBUG
                        acetype = "EVERYONE@";
#endif
                        match = true;
                } else {
                        if (ace->ace_idsize != sizeof(tid))
                                continue;
#ifdef ACE_DEBUG
                        show_tid = true;
#endif
                        memcpy(&tid, &ace->ace_idbytes, sizeof(tid));
                        if (ace->ace_flags & L9P_ACEF_IDENTIFIER_GROUP) {
#ifdef ACE_DEBUG
                                acetype = "group";
#endif
                                match = l9p_ingroup(tid, gid, gids, ngids);
                        } else {
#ifdef ACE_DEBUG
                                acetype = "user";
#endif
                                match = tid == uid;
                        }
                }
                /*
                 * If this ACE applies to us, check remaining bits.
                 * If any of those bits also apply, check the type:
                 * DENY means "stop now", ALLOW means allow these bits
                 * and keep checking.
                 */
#ifdef ACE_DEBUG
                allowdeny = ace->ace_type == L9P_ACET_ACCESS_DENIED ?
                    "deny" : "allow";
#endif
                if (match && (ace->ace_mask & (uint32_t)mask) != 0) {
#ifdef ACE_DEBUG
                        if (show_tid)
                                L9P_LOG(L9P_DEBUG,
                                    "ACE: %s %s %d: mask 0x%x ace_mask 0x%x",
                                    allowdeny, acetype, (int)tid,
                                    (u_int)mask, (u_int)ace->ace_mask);
                        else
                                L9P_LOG(L9P_DEBUG,
                                    "ACE: %s %s: mask 0x%x ace_mask 0x%x",
                                    allowdeny, acetype,
                                    (u_int)mask, (u_int)ace->ace_mask);
#endif
                        if (ace->ace_type == L9P_ACET_ACCESS_DENIED)
                                return (-1);
                        mask &= ~ace->ace_mask;
#ifdef ACE_DEBUG
                        L9P_LOG(L9P_DEBUG, "clear 0x%x: now mask=0x%x",
                            (u_int)ace->ace_mask, (u_int)mask);
#endif
                } else {
#ifdef ACE_DEBUG
                        if (show_tid)
                                L9P_LOG(L9P_DEBUG,
                                    "ACE: SKIP %s %s %d: "
                                    "match %d mask 0x%x ace_mask 0x%x",
                                    allowdeny, acetype, (int)tid,
                                    (int)match, (u_int)mask,
                                    (u_int)ace->ace_mask);
                        else
                                L9P_LOG(L9P_DEBUG,
                                    "ACE: SKIP %s %s: "
                                    "match %d mask 0x%x ace_mask 0x%x",
                                    allowdeny, acetype,
                                    (int)match, (u_int)mask,
                                    (u_int)ace->ace_mask);
#endif
                }
        }

        /* Return 1 if access definitely granted. */
#ifdef ACE_DEBUG
        L9P_LOG(L9P_DEBUG, "ACE: end of ACEs, mask now 0x%x: %s",
            mask, mask ? "no-definitive-answer" : "ALLOW");
#endif
        return (mask == 0 ? 1 : 0);
}

/*
 * Test against ACLs.
 *
 * The return value is normally 0 (access allowed) or EPERM
 * (access denied), so it could just be a boolean....
 *
 * For "make new dir in dir" and "remove dir in dir", you must
 * set the mask to test the directory permissions (not ADD_FILE but
 * ADD_SUBDIRECTORY, and DELETE_CHILD).  For "make new file in dir"
 * you must set the opmask to test file ADD_FILE.
 *
 * The L9P_ACE_DELETE flag means "can delete this thing"; it's not
 * clear whether it should override the parent directory's ACL if
 * any.  In our case it does not, but a caller may try
 * L9P_ACE_DELETE_CHILD (separately, on its own) and then a
 * (second, separate) L9P_ACE_DELETE, to make the permissions work
 * as "or" instead of "and".
 *
 * Pass a NULL parent/pstat if they are not applicable, e.g.,
 * for doing operations on an existing file, such as reading or
 * writing data or attributes.  Pass in a null child/cstat if
 * that's not applicable, such as creating a new file/dir.
 *
 * NB: it's probably wise to allow the owner of any file to update
 * the ACLs of that file, but we leave that test to the caller.
 */
int l9p_acl_check_access(int32_t opmask, struct l9p_acl_check_args *args)
{
        struct l9p_acl *parent, *child;
        struct stat *pstat, *cstat;
        int32_t pop, cop;
        size_t ngids;
        uid_t uid;
        gid_t gid, *gids;
        int panswer, canswer;

        assert(opmask != 0);
        parent = args->aca_parent;
        pstat = args->aca_pstat;
        child = args->aca_child;
        cstat = args->aca_cstat;
        uid = args->aca_uid;
        gid = args->aca_gid;
        gids = args->aca_groups;
        ngids = args->aca_ngroups;

#ifdef ACE_DEBUG
        L9P_LOG(L9P_DEBUG,
            "l9p_acl_check_access: opmask=0x%x uid=%ld gid=%ld ngids=%zd",
            (u_int)opmask, (long)uid, (long)gid, ngids);
#endif
        /*
         * If caller said "superuser semantics", check that first.
         * Note that we apply them regardless of ACLs.
         */
        if (uid == 0 && args->aca_superuser)
                return (0);

        /*
         * If told to ignore ACLs and use only stat-based permissions,
         * discard any non-NULL ACL pointers.
         *
         * This will need some fancying up when we support POSIX ACLs.
         */
        if ((args->aca_aclmode & L9P_ACM_NFS_ACL) == 0)
                parent = child = NULL;

        assert(parent == NULL || parent->acl_acetype == L9P_ACLTYPE_NFSv4);
        assert(parent == NULL || pstat != NULL);
        assert(child == NULL || child->acl_acetype == L9P_ACLTYPE_NFSv4);
        assert(child == NULL || cstat != NULL);
        assert(pstat != NULL || cstat != NULL);

        /*
         * If the operation is UNLINK we should have either both ACLs
         * or no ACLs, but we won't require that here.
         *
         * If a parent ACL is supplied, it's a directory by definition.
         * Make sure we're allowed to do this there, whatever this is.
         * If a child ACL is supplied, check it too.  Note that the
         * DELETE permission only applies in the child though, not
         * in the parent, and the DELETE_CHILD only applies in the
         * parent.
         */
        pop = cop = opmask;
        if (parent != NULL || pstat != NULL) {
                /*
                 * Remove child-only bits from parent op and
                 * parent-only bits from child op.
                 *
                 * L9P_ACE_DELETE is child-only.
                 *
                 * L9P_ACE_DELETE_CHILD is parent-only, and three data
                 * access bits overlap with three directory access bits.
                 * We should have child==NULL && cstat==NULL, so the
                 * three data bits should be redundant, but it's
                 * both trivial and safest to remove them anyway.
                 */
                pop &= ~L9P_ACE_DELETE;
                cop &= ~(L9P_ACE_DELETE_CHILD | L9P_ACE_LIST_DIRECTORY |
                    L9P_ACE_ADD_FILE | L9P_ACE_ADD_SUBDIRECTORY);
        } else {
                /*
                 * Remove child-only bits from parent op.  We need
                 * not bother since we just found we have no parent
                 * and no pstat, and hence won't actually *use* pop.
                 *
                 * pop &= ~(L9P_ACE_READ_DATA | L9P_ACE_WRITE_DATA |
                 *     L9P_ACE_APPEND_DATA);
                 */
        }
        panswer = 0;
        canswer = 0;
        if (parent != NULL)
                panswer = l9p_check_aces(pop, parent, pstat,
                    uid, gid, gids, ngids);
        if (child != NULL)
                canswer = l9p_check_aces(cop, child, cstat,
                    uid, gid, gids, ngids);

        if (panswer || canswer) {
                /*
                 * Got a definitive answer from parent and/or
                 * child ACLs.  We're not quite done yet though.
                 */
                if (opmask == L9P_ACOP_UNLINK) {
                        /*
                         * For UNLINK, we can get an allow from child
                         * and deny from parent, or vice versa.  It's
                         * not 100% clear how to handle the two-answer
                         * case.  ZFS says that if either says "allow",
                         * we allow, and if both definitely say "deny",
                         * we deny.  This makes sense, so we do that
                         * here for all cases, even "strict".
                         */
                        if (panswer > 0 || canswer > 0)
                                return (0);
                        if (panswer < 0 && canswer < 0)
                                return (EPERM);
                        /* non-definitive answer from one! move on */
                } else {
                        /*
                         * Have at least one definitive answer, and
                         * should have only one; obey whichever
                         * one it is.
                         */
                        if (panswer)
                                return (panswer < 0 ? EPERM : 0);
                        return (canswer < 0 ? EPERM : 0);
                }
        }

        /*
         * No definitive answer from ACLs alone.  Check for ZFS style
         * permissions checking and an "UNLINK" operation under ACLs.
         * If so, find write-and-execute permission on parent.
         * Note that WRITE overlaps with ADD_FILE -- that's ZFS's
         * way of saying "allow write to dir" -- but EXECUTE is
         * separate from LIST_DIRECTORY, so that's at least a little
         * bit cleaner.
         *
         * Note also that only a definitive yes (both bits are
         * explicitly allowed) results in granting unlink, and
         * a definitive no (at least one bit explicitly denied)
         * results in EPERM.  Only "no answer" moves on.
         */
        if ((args->aca_aclmode & L9P_ACM_ZFS_ACL) &&
            opmask == L9P_ACOP_UNLINK && parent != NULL) {
                panswer = l9p_check_aces(L9P_ACE_ADD_FILE | L9P_ACE_EXECUTE,
                    parent, pstat, uid, gid, gids, ngids);
                if (panswer)
                        return (panswer < 0 ? EPERM : 0);
        }

        /*
         * No definitive answer from ACLs.
         *
         * Try POSIX style rwx permissions if allowed.  This should
         * be rare, occurring mainly when caller supplied no ACLs
         * or set the mode to suppress them.
         *
         * The stat to check is the parent's if we don't have a child
         * (i.e., this is a dir op), or if the DELETE_CHILD bit is set
         * (i.e., this is an unlink or similar).  Otherwise it's the
         * child's.
         */
        if (args->aca_aclmode & L9P_ACM_STAT_MODE) {
                struct stat *st;
                int rwx, bits;

                rwx = l9p_ace_mask_to_rwx(opmask);
                if ((st = cstat) == NULL || (opmask & L9P_ACE_DELETE_CHILD))
                        st = pstat;
                if (uid == st->st_uid)
                        bits = (st->st_mode >> 6) & 7;
                else if (l9p_ingroup(st->st_gid, gid, gids, ngids))
                        bits = (st->st_mode >> 3) & 7;
                else
                        bits = st->st_mode & 7;
                /*
                 * If all the desired bits are set, we're OK.
                 */
                if ((rwx & bits) == rwx)
                        return (0);
        }

        /* all methods have failed, return EPERM */
        return (EPERM);
}

/*
 * Collapse fancy ACL operation mask down to simple Unix bits.
 *
 * Directory operations don't map that well.  However, listing
 * a directory really does require read permission, and adding
 * or deleting files really does require write permission, so
 * this is probably sufficient.
 */
int
l9p_ace_mask_to_rwx(int32_t opmask)
{
        int rwx = 0;

        if (opmask &
            (L9P_ACE_READ_DATA | L9P_ACE_READ_NAMED_ATTRS |
             L9P_ACE_READ_ATTRIBUTES | L9P_ACE_READ_ACL))
                rwx |= 4;
        if (opmask &
            (L9P_ACE_WRITE_DATA | L9P_ACE_APPEND_DATA |
             L9P_ACE_ADD_FILE | L9P_ACE_ADD_SUBDIRECTORY |
             L9P_ACE_DELETE | L9P_ACE_DELETE_CHILD |
             L9P_ACE_WRITE_NAMED_ATTRS | L9P_ACE_WRITE_ATTRIBUTES |
             L9P_ACE_WRITE_ACL))
                rwx |= 2;
        if (opmask & L9P_ACE_EXECUTE)
                rwx |= 1;
        return (rwx);
}

#if defined(__FreeBSD__) || defined(__illumos__)
/*
 * Allocate new ACL holder and ACEs.
 */
static struct l9p_acl *
l9p_new_acl(uint32_t acetype, uint32_t aceasize)
{
        struct l9p_acl *ret;
        size_t asize, size;

        asize = aceasize * sizeof(struct l9p_ace);
        size = sizeof(struct l9p_acl) + asize;
        ret = malloc(size);
        if (ret != NULL) {
                ret->acl_acetype = acetype;
                ret->acl_nace = 0;
                ret->acl_aceasize = aceasize;
        }
        return (ret);
}
#endif

#ifdef __FreeBSD__
/*
 * Expand ACL to accomodate more entries.
 *
 * Currently won't shrink, only grow, so it's a fast no-op until
 * we hit the allocated size.  After that, it's best to grow in
 * big chunks, or this will be O(n**2).
 */
static struct l9p_acl *
l9p_growacl(struct l9p_acl *acl, uint32_t aceasize)
{
        struct l9p_acl *tmp;
        size_t asize, size;

        if (acl->acl_aceasize < aceasize) {
                asize = aceasize * sizeof(struct l9p_ace);
                size = sizeof(struct l9p_acl) + asize;
                tmp = realloc(acl, size);
                if (tmp == NULL)
                        free(acl);
                acl = tmp;
        }
        return (acl);
}

/*
 * Annoyingly, there's no POSIX-standard way to count the number
 * of ACEs in a system ACL other than to walk through them all.
 * This is silly, but at least 2n is still O(n), and the walk is
 * short.  (If the system ACL mysteriously grows, we'll handle
 * that OK via growacl(), too.)
 */
static int
l9p_count_aces(acl_t sysacl)
{
        acl_entry_t entry;
        uint32_t n;
        int id;

        id = ACL_FIRST_ENTRY;
        for (n = 0; acl_get_entry(sysacl, id, &entry) == 1; n++)
                id = ACL_NEXT_ENTRY;

        return ((int)n);
}

/*
 * Create ACL with ACEs from the given acl_t.  We use the given
 * convert function on each ACE.
 */
static struct l9p_acl *
l9p_sysacl_to_acl(int acetype, acl_t sysacl, econvertfn *convert)
{
        struct l9p_acl *acl;
        acl_entry_t entry;
        uint32_t n;
        int error, id;

        acl = l9p_new_acl((uint32_t)acetype, (uint32_t)l9p_count_aces(sysacl));
        if (acl == NULL)
                return (NULL);
        id = ACL_FIRST_ENTRY;
        for (n = 0;;) {
                if (acl_get_entry(sysacl, id, &entry) != 1)
                        break;
                acl = l9p_growacl(acl, n + 1);
                if (acl == NULL)
                        return (NULL);
                error = (*convert)(entry, &acl->acl_aces[n]);
                id = ACL_NEXT_ENTRY;
                if (error == 0)
                        n++;
        }
        acl->acl_nace = n;
        return (acl);
}
#endif

#if defined(HAVE_POSIX_ACLS) && 0 /* not yet */
struct l9p_acl *
l9p_posix_acl_to_acl(acl_t sysacl)
{
}
#endif

#if defined(HAVE_FREEBSD_ACLS)
static int
l9p_frombsdnfs4(acl_entry_t sysace, struct l9p_ace *ace)
{
        acl_tag_t tag;                  /* e.g., USER_OBJ, GROUP, etc */
        acl_entry_type_t entry_type;    /* e.g., allow/deny */
        acl_permset_t absdperm;
        acl_flagset_t absdflag;
        acl_perm_t bsdperm;             /* e.g., READ_DATA */
        acl_flag_t bsdflag;             /* e.g., FILE_INHERIT_ACE */
        uint32_t flags, mask;
        int error;
        uid_t uid, *aid;

        error = acl_get_tag_type(sysace, &tag);
        if (error == 0)
                error = acl_get_entry_type_np(sysace, &entry_type);
        if (error == 0)
                error = acl_get_flagset_np(sysace, &absdflag);
        if (error == 0)
                error = acl_get_permset(sysace, &absdperm);
        if (error)
                return (error);

        flags = 0;
        uid = 0;
        aid = NULL;

        /* move user/group/everyone + id-is-group-id into flags */
        switch (tag) {
        case ACL_USER_OBJ:
                flags |= L9P_ACEF_OWNER;
                break;
        case ACL_GROUP_OBJ:
                flags |= L9P_ACEF_GROUP;
                break;
        case ACL_EVERYONE:
                flags |= L9P_ACEF_EVERYONE;
                break;
        case ACL_GROUP:
                flags |= L9P_ACEF_IDENTIFIER_GROUP;
                /* FALLTHROUGH */
        case ACL_USER:
                aid = acl_get_qualifier(sysace); /* ugh, this malloc()s */
                if (aid == NULL)
                        return (ENOMEM);
                uid = *(uid_t *)aid;
                free(aid);
                aid = &uid;
                break;
        default:
                return (EINVAL);        /* can't happen */
        }

        switch (entry_type) {

        case ACL_ENTRY_TYPE_ALLOW:
                ace->ace_type = L9P_ACET_ACCESS_ALLOWED;
                break;

        case ACL_ENTRY_TYPE_DENY:
                ace->ace_type = L9P_ACET_ACCESS_DENIED;
                break;

        case ACL_ENTRY_TYPE_AUDIT:
                ace->ace_type = L9P_ACET_SYSTEM_AUDIT;
                break;

        case ACL_ENTRY_TYPE_ALARM:
                ace->ace_type = L9P_ACET_SYSTEM_ALARM;
                break;

        default:
                return (EINVAL);        /* can't happen */
        }

        /* transform remaining BSD flags to internal NFS-y form */
        bsdflag = *absdflag;
        if (bsdflag & ACL_ENTRY_FILE_INHERIT)
                flags |= L9P_ACEF_FILE_INHERIT_ACE;
        if (bsdflag & ACL_ENTRY_DIRECTORY_INHERIT)
                flags |= L9P_ACEF_DIRECTORY_INHERIT_ACE;
        if (bsdflag & ACL_ENTRY_NO_PROPAGATE_INHERIT)
                flags |= L9P_ACEF_NO_PROPAGATE_INHERIT_ACE;
        if (bsdflag & ACL_ENTRY_INHERIT_ONLY)
                flags |= L9P_ACEF_INHERIT_ONLY_ACE;
        if (bsdflag & ACL_ENTRY_SUCCESSFUL_ACCESS)
                flags |= L9P_ACEF_SUCCESSFUL_ACCESS_ACE_FLAG;
        if (bsdflag & ACL_ENTRY_FAILED_ACCESS)
                flags |= L9P_ACEF_FAILED_ACCESS_ACE_FLAG;
        ace->ace_flags = flags;

        /*
         * Transform BSD permissions to ace_mask.  Note that directory
         * vs file bits are the same in both sets, so we don't need
         * to worry about that, at least.
         *
         * There seem to be no BSD equivalents for WRITE_RETENTION
         * and WRITE_RETENTION_HOLD.
         */
        mask = 0;
        bsdperm = *absdperm;
        if (bsdperm & ACL_READ_DATA)
                mask |= L9P_ACE_READ_DATA;
        if (bsdperm & ACL_WRITE_DATA)
                mask |= L9P_ACE_WRITE_DATA;
        if (bsdperm & ACL_APPEND_DATA)
                mask |= L9P_ACE_APPEND_DATA;
        if (bsdperm & ACL_READ_NAMED_ATTRS)
                mask |= L9P_ACE_READ_NAMED_ATTRS;
        if (bsdperm & ACL_WRITE_NAMED_ATTRS)
                mask |= L9P_ACE_WRITE_NAMED_ATTRS;
        if (bsdperm & ACL_EXECUTE)
                mask |= L9P_ACE_EXECUTE;
        if (bsdperm & ACL_DELETE_CHILD)
                mask |= L9P_ACE_DELETE_CHILD;
        if (bsdperm & ACL_READ_ATTRIBUTES)
                mask |= L9P_ACE_READ_ATTRIBUTES;
        if (bsdperm & ACL_WRITE_ATTRIBUTES)
                mask |= L9P_ACE_WRITE_ATTRIBUTES;
        /* L9P_ACE_WRITE_RETENTION */
        /* L9P_ACE_WRITE_RETENTION_HOLD */
        /* 0x00800 */
        if (bsdperm & ACL_DELETE)
                mask |= L9P_ACE_DELETE;
        if (bsdperm & ACL_READ_ACL)
                mask |= L9P_ACE_READ_ACL;
        if (bsdperm & ACL_WRITE_ACL)
                mask |= L9P_ACE_WRITE_ACL;
        if (bsdperm & ACL_WRITE_OWNER)
                mask |= L9P_ACE_WRITE_OWNER;
        if (bsdperm & ACL_SYNCHRONIZE)
                mask |= L9P_ACE_SYNCHRONIZE;
        ace->ace_mask = mask;

        /* fill in variable-size user or group ID bytes */
        if (aid == NULL)
                ace->ace_idsize = 0;
        else {
                ace->ace_idsize = sizeof(uid);
                memcpy(&ace->ace_idbytes[0], aid, sizeof(uid));
        }

        return (0);
}

struct l9p_acl *
l9p_freebsd_nfsv4acl_to_acl(acl_t sysacl)
{

        return (l9p_sysacl_to_acl(L9P_ACLTYPE_NFSv4, sysacl, l9p_frombsdnfs4));
}
#endif

#if defined(HAVE_DARWIN_ACLS) && 0 /* not yet */
struct l9p_acl *
l9p_darwin_nfsv4acl_to_acl(acl_t sysacl)
{
}
#endif

#if defined(HAVE__ILLUMOS_ACLS)

static struct {
        uint16_t ace_flag;
        uint32_t l9_flag;
} ace_flag_tbl[] = {
        { ACE_FILE_INHERIT_ACE,         L9P_ACEF_FILE_INHERIT_ACE },
        { ACE_DIRECTORY_INHERIT_ACE,    L9P_ACEF_DIRECTORY_INHERIT_ACE },
        { ACE_NO_PROPAGATE_INHERIT_ACE, L9P_ACEF_NO_PROPAGATE_INHERIT_ACE },
        { ACE_INHERIT_ONLY_ACE,         L9P_ACEF_INHERIT_ONLY_ACE },
        { ACE_SUCCESSFUL_ACCESS_ACE_FLAG,
            L9P_ACEF_SUCCESSFUL_ACCESS_ACE_FLAG },
        { ACE_IDENTIFIER_GROUP,         L9P_ACEF_IDENTIFIER_GROUP },
        /* There doesn't appear to be an equivalent for ACE_INHERITED_ACE */
        { ACE_OWNER,                    L9P_ACEF_OWNER },
        { ACE_GROUP,                    L9P_ACEF_GROUP },
        { ACE_EVERYONE,                 L9P_ACEF_EVERYONE }
};

struct l9p_acl *
l9p_illumos_nfsv4acl_to_acl(acl_t *sysacl)
{
        struct l9p_acl *l9acl;
        struct l9p_ace *l9ace;
        ace_t *ent;
        int i, j;

        /* We only support NFSv4 ACLs.. so don't try this on UFS */
        if (sysacl->acl_type != ACE_T)
                return (NULL);

        l9acl = l9p_new_acl(L9P_ACLTYPE_NFSv4, sysacl->acl_cnt);
        if (l9acl == NULL)
                return (NULL);

        ent = sysacl->acl_aclp;
        l9ace = l9acl->acl_aces;
        for (i = 0; i < sysacl->acl_cnt; i++, ent++, l9ace++) {
                switch (ent->a_type) {
                case ACE_ACCESS_ALLOWED_ACE_TYPE:
                        l9ace->ace_type = L9P_ACET_ACCESS_ALLOWED;
                        break;
                case ACE_ACCESS_DENIED_ACE_TYPE:
                        l9ace->ace_type = L9P_ACET_ACCESS_DENIED;
                        break;
                case ACE_SYSTEM_AUDIT_ACE_TYPE:
                        l9ace->ace_type = L9P_ACET_SYSTEM_AUDIT;
                        break;
                case ACE_SYSTEM_ALARM_ACE_TYPE:
                        l9ace->ace_type = L9P_ACET_SYSTEM_ALARM;
                        break;
                default:
                        L9P_LOG(L9P_ERROR, "invalid ACL type");
                        l9p_acl_free(l9acl);
                        return (NULL);
                }

                l9ace->ace_flags = 0;
                for (j = 0; j < ARRAY_SIZE(ace_flag_tbl); j++) {
                        if ((ent->a_flags & ace_flag_tbl[j].ace_flag) != 0)
                                l9ace->ace_flags |= ace_flag_tbl[j].l9_flag;
                }

                /*
                 * In a bit of good fortune, the bit values for ace_t masks
                 * and l9p masks are the same (l9p does have WRITE_RETENTION
                 * and WRITE_RETENTION_HOLD which aren't used -- we're also
                 * going ace_t->l9p so they dont matter in this context).
                 */
                l9ace->ace_mask = ent->a_access_mask;
                l9ace->ace_idsize = sizeof (ent->a_who);
                memcpy(l9acl->acl_aces, &ent->a_who, sizeof (ent->a_who));
        }

        return (l9acl);
}
#endif