/*1* Copyright 2016 Chris Torek <[email protected]>2* All rights reserved3*4* Redistribution and use in source and binary forms, with or without5* modification, are permitted providing that the following conditions6* are met:7* 1. Redistributions of source code must retain the above copyright8* notice, this list of conditions and the following disclaimer.9* 2. Redistributions in binary form must reproduce the above copyright10* notice, this list of conditions and the following disclaimer in the11* documentation and/or other materials provided with the distribution.12*13* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR14* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED15* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE16* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY17* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL18* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS19* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)20* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,21* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING22* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE23* POSSIBILITY OF SUCH DAMAGE.24*/2526#include <assert.h>27#include <stdlib.h>28#include <string.h>29#include <errno.h>30#include <sys/types.h>31#include <sys/acl.h>32#include <sys/stat.h>3334#include "lib9p.h"35#include "lib9p_impl.h"36#include "genacl.h"37#include "fid.h"38#include "log.h"3940typedef int econvertfn(acl_entry_t, struct l9p_ace *);4142#ifndef __APPLE__43static struct l9p_acl *l9p_new_acl(uint32_t acetype, uint32_t aceasize);44static struct l9p_acl *l9p_growacl(struct l9p_acl *acl, uint32_t aceasize);45static int l9p_count_aces(acl_t sysacl);46static struct l9p_acl *l9p_sysacl_to_acl(int, acl_t, econvertfn *);47#endif48static bool l9p_ingroup(gid_t tid, gid_t gid, gid_t *gids, size_t ngids);49static int l9p_check_aces(int32_t mask, struct l9p_acl *acl, struct stat *st,50uid_t uid, gid_t gid, gid_t *gids, size_t ngids);5152void53l9p_acl_free(struct l9p_acl *acl)54{5556free(acl);57}5859/*60* Is the given group ID tid (test-id) any of the gid's in agids?61*/62static bool63l9p_ingroup(gid_t tid, gid_t gid, gid_t *gids, size_t ngids)64{65size_t i;6667if (tid == gid)68return (true);69for (i = 0; i < ngids; i++)70if (tid == gids[i])71return (true);72return (false);73}7475/* #define ACE_DEBUG */7677/*78* Note that NFSv4 tests are done on a "first match" basis.79* That is, we check each ACE sequentially until we run out80* of ACEs, or find something explicitly denied (DENIED!),81* or have cleared out all our attempt-something bits. Once82* we come across an ALLOW entry for the bits we're trying,83* we clear those from the bits we're still looking for, in84* the order they appear.85*86* The result is either "definitely allowed" (we cleared87* all the bits), "definitely denied" (we hit a deny with88* some or all of the bits), or "unspecified". We89* represent these three states as +1 (positive = yes = allow),90* -1 (negative = no = denied), or 0 (no strong answer).91*92* For our caller's convenience, if we are called with a93* mask of 0, we return 0 (no answer).94*/95static int96l9p_check_aces(int32_t mask, struct l9p_acl *acl, struct stat *st,97uid_t uid, gid_t gid, gid_t *gids, size_t ngids)98{99uint32_t i;100struct l9p_ace *ace;101#ifdef ACE_DEBUG102const char *acetype, *allowdeny;103bool show_tid;104#endif105bool match;106uid_t tid;107108if (mask == 0)109return (0);110111for (i = 0; mask != 0 && i < acl->acl_nace; i++) {112ace = &acl->acl_aces[i];113switch (ace->ace_type) {114case L9P_ACET_ACCESS_ALLOWED:115case L9P_ACET_ACCESS_DENIED:116break;117default:118/* audit, alarm - ignore */119continue;120}121#ifdef ACE_DEBUG122show_tid = false;123#endif124if (ace->ace_flags & L9P_ACEF_OWNER) {125#ifdef ACE_DEBUG126acetype = "OWNER@";127#endif128match = st->st_uid == uid;129} else if (ace->ace_flags & L9P_ACEF_GROUP) {130#ifdef ACE_DEBUG131acetype = "GROUP@";132#endif133match = l9p_ingroup(st->st_gid, gid, gids, ngids);134} else if (ace->ace_flags & L9P_ACEF_EVERYONE) {135#ifdef ACE_DEBUG136acetype = "EVERYONE@";137#endif138match = true;139} else {140if (ace->ace_idsize != sizeof(tid))141continue;142#ifdef ACE_DEBUG143show_tid = true;144#endif145memcpy(&tid, &ace->ace_idbytes, sizeof(tid));146if (ace->ace_flags & L9P_ACEF_IDENTIFIER_GROUP) {147#ifdef ACE_DEBUG148acetype = "group";149#endif150match = l9p_ingroup(tid, gid, gids, ngids);151} else {152#ifdef ACE_DEBUG153acetype = "user";154#endif155match = tid == uid;156}157}158/*159* If this ACE applies to us, check remaining bits.160* If any of those bits also apply, check the type:161* DENY means "stop now", ALLOW means allow these bits162* and keep checking.163*/164#ifdef ACE_DEBUG165allowdeny = ace->ace_type == L9P_ACET_ACCESS_DENIED ?166"deny" : "allow";167#endif168if (match && (ace->ace_mask & (uint32_t)mask) != 0) {169#ifdef ACE_DEBUG170if (show_tid)171L9P_LOG(L9P_DEBUG,172"ACE: %s %s %d: mask 0x%x ace_mask 0x%x",173allowdeny, acetype, (int)tid,174(u_int)mask, (u_int)ace->ace_mask);175else176L9P_LOG(L9P_DEBUG,177"ACE: %s %s: mask 0x%x ace_mask 0x%x",178allowdeny, acetype,179(u_int)mask, (u_int)ace->ace_mask);180#endif181if (ace->ace_type == L9P_ACET_ACCESS_DENIED)182return (-1);183mask &= ~ace->ace_mask;184#ifdef ACE_DEBUG185L9P_LOG(L9P_DEBUG, "clear 0x%x: now mask=0x%x",186(u_int)ace->ace_mask, (u_int)mask);187#endif188} else {189#ifdef ACE_DEBUG190if (show_tid)191L9P_LOG(L9P_DEBUG,192"ACE: SKIP %s %s %d: "193"match %d mask 0x%x ace_mask 0x%x",194allowdeny, acetype, (int)tid,195(int)match, (u_int)mask,196(u_int)ace->ace_mask);197else198L9P_LOG(L9P_DEBUG,199"ACE: SKIP %s %s: "200"match %d mask 0x%x ace_mask 0x%x",201allowdeny, acetype,202(int)match, (u_int)mask,203(u_int)ace->ace_mask);204#endif205}206}207208/* Return 1 if access definitely granted. */209#ifdef ACE_DEBUG210L9P_LOG(L9P_DEBUG, "ACE: end of ACEs, mask now 0x%x: %s",211mask, mask ? "no-definitive-answer" : "ALLOW");212#endif213return (mask == 0 ? 1 : 0);214}215216/*217* Test against ACLs.218*219* The return value is normally 0 (access allowed) or EPERM220* (access denied), so it could just be a boolean....221*222* For "make new dir in dir" and "remove dir in dir", you must223* set the mask to test the directory permissions (not ADD_FILE but224* ADD_SUBDIRECTORY, and DELETE_CHILD). For "make new file in dir"225* you must set the opmask to test file ADD_FILE.226*227* The L9P_ACE_DELETE flag means "can delete this thing"; it's not228* clear whether it should override the parent directory's ACL if229* any. In our case it does not, but a caller may try230* L9P_ACE_DELETE_CHILD (separately, on its own) and then a231* (second, separate) L9P_ACE_DELETE, to make the permissions work232* as "or" instead of "and".233*234* Pass a NULL parent/pstat if they are not applicable, e.g.,235* for doing operations on an existing file, such as reading or236* writing data or attributes. Pass in a null child/cstat if237* that's not applicable, such as creating a new file/dir.238*239* NB: it's probably wise to allow the owner of any file to update240* the ACLs of that file, but we leave that test to the caller.241*/242int l9p_acl_check_access(int32_t opmask, struct l9p_acl_check_args *args)243{244struct l9p_acl *parent, *child;245struct stat *pstat, *cstat;246int32_t pop, cop;247size_t ngids;248uid_t uid;249gid_t gid, *gids;250int panswer, canswer;251252assert(opmask != 0);253parent = args->aca_parent;254pstat = args->aca_pstat;255child = args->aca_child;256cstat = args->aca_cstat;257uid = args->aca_uid;258gid = args->aca_gid;259gids = args->aca_groups;260ngids = args->aca_ngroups;261262#ifdef ACE_DEBUG263L9P_LOG(L9P_DEBUG,264"l9p_acl_check_access: opmask=0x%x uid=%ld gid=%ld ngids=%zd",265(u_int)opmask, (long)uid, (long)gid, ngids);266#endif267/*268* If caller said "superuser semantics", check that first.269* Note that we apply them regardless of ACLs.270*/271if (uid == 0 && args->aca_superuser)272return (0);273274/*275* If told to ignore ACLs and use only stat-based permissions,276* discard any non-NULL ACL pointers.277*278* This will need some fancying up when we support POSIX ACLs.279*/280if ((args->aca_aclmode & L9P_ACM_NFS_ACL) == 0)281parent = child = NULL;282283assert(parent == NULL || parent->acl_acetype == L9P_ACLTYPE_NFSv4);284assert(parent == NULL || pstat != NULL);285assert(child == NULL || child->acl_acetype == L9P_ACLTYPE_NFSv4);286assert(child == NULL || cstat != NULL);287assert(pstat != NULL || cstat != NULL);288289/*290* If the operation is UNLINK we should have either both ACLs291* or no ACLs, but we won't require that here.292*293* If a parent ACL is supplied, it's a directory by definition.294* Make sure we're allowed to do this there, whatever this is.295* If a child ACL is supplied, check it too. Note that the296* DELETE permission only applies in the child though, not297* in the parent, and the DELETE_CHILD only applies in the298* parent.299*/300pop = cop = opmask;301if (parent != NULL || pstat != NULL) {302/*303* Remove child-only bits from parent op and304* parent-only bits from child op.305*306* L9P_ACE_DELETE is child-only.307*308* L9P_ACE_DELETE_CHILD is parent-only, and three data309* access bits overlap with three directory access bits.310* We should have child==NULL && cstat==NULL, so the311* three data bits should be redundant, but it's312* both trivial and safest to remove them anyway.313*/314pop &= ~L9P_ACE_DELETE;315cop &= ~(L9P_ACE_DELETE_CHILD | L9P_ACE_LIST_DIRECTORY |316L9P_ACE_ADD_FILE | L9P_ACE_ADD_SUBDIRECTORY);317} else {318/*319* Remove child-only bits from parent op. We need320* not bother since we just found we have no parent321* and no pstat, and hence won't actually *use* pop.322*323* pop &= ~(L9P_ACE_READ_DATA | L9P_ACE_WRITE_DATA |324* L9P_ACE_APPEND_DATA);325*/326}327panswer = 0;328canswer = 0;329if (parent != NULL)330panswer = l9p_check_aces(pop, parent, pstat,331uid, gid, gids, ngids);332if (child != NULL)333canswer = l9p_check_aces(cop, child, cstat,334uid, gid, gids, ngids);335336if (panswer || canswer) {337/*338* Got a definitive answer from parent and/or339* child ACLs. We're not quite done yet though.340*/341if (opmask == L9P_ACOP_UNLINK) {342/*343* For UNLINK, we can get an allow from child344* and deny from parent, or vice versa. It's345* not 100% clear how to handle the two-answer346* case. ZFS says that if either says "allow",347* we allow, and if both definitely say "deny",348* we deny. This makes sense, so we do that349* here for all cases, even "strict".350*/351if (panswer > 0 || canswer > 0)352return (0);353if (panswer < 0 && canswer < 0)354return (EPERM);355/* non-definitive answer from one! move on */356} else {357/*358* Have at least one definitive answer, and359* should have only one; obey whichever360* one it is.361*/362if (panswer)363return (panswer < 0 ? EPERM : 0);364return (canswer < 0 ? EPERM : 0);365}366}367368/*369* No definitive answer from ACLs alone. Check for ZFS style370* permissions checking and an "UNLINK" operation under ACLs.371* If so, find write-and-execute permission on parent.372* Note that WRITE overlaps with ADD_FILE -- that's ZFS's373* way of saying "allow write to dir" -- but EXECUTE is374* separate from LIST_DIRECTORY, so that's at least a little375* bit cleaner.376*377* Note also that only a definitive yes (both bits are378* explicitly allowed) results in granting unlink, and379* a definitive no (at least one bit explicitly denied)380* results in EPERM. Only "no answer" moves on.381*/382if ((args->aca_aclmode & L9P_ACM_ZFS_ACL) &&383opmask == L9P_ACOP_UNLINK && parent != NULL) {384panswer = l9p_check_aces(L9P_ACE_ADD_FILE | L9P_ACE_EXECUTE,385parent, pstat, uid, gid, gids, ngids);386if (panswer)387return (panswer < 0 ? EPERM : 0);388}389390/*391* No definitive answer from ACLs.392*393* Try POSIX style rwx permissions if allowed. This should394* be rare, occurring mainly when caller supplied no ACLs395* or set the mode to suppress them.396*397* The stat to check is the parent's if we don't have a child398* (i.e., this is a dir op), or if the DELETE_CHILD bit is set399* (i.e., this is an unlink or similar). Otherwise it's the400* child's.401*/402if (args->aca_aclmode & L9P_ACM_STAT_MODE) {403struct stat *st;404int rwx, bits;405406rwx = l9p_ace_mask_to_rwx(opmask);407if ((st = cstat) == NULL || (opmask & L9P_ACE_DELETE_CHILD))408st = pstat;409if (uid == st->st_uid)410bits = (st->st_mode >> 6) & 7;411else if (l9p_ingroup(st->st_gid, gid, gids, ngids))412bits = (st->st_mode >> 3) & 7;413else414bits = st->st_mode & 7;415/*416* If all the desired bits are set, we're OK.417*/418if ((rwx & bits) == rwx)419return (0);420}421422/* all methods have failed, return EPERM */423return (EPERM);424}425426/*427* Collapse fancy ACL operation mask down to simple Unix bits.428*429* Directory operations don't map that well. However, listing430* a directory really does require read permission, and adding431* or deleting files really does require write permission, so432* this is probably sufficient.433*/434int435l9p_ace_mask_to_rwx(int32_t opmask)436{437int rwx = 0;438439if (opmask &440(L9P_ACE_READ_DATA | L9P_ACE_READ_NAMED_ATTRS |441L9P_ACE_READ_ATTRIBUTES | L9P_ACE_READ_ACL))442rwx |= 4;443if (opmask &444(L9P_ACE_WRITE_DATA | L9P_ACE_APPEND_DATA |445L9P_ACE_ADD_FILE | L9P_ACE_ADD_SUBDIRECTORY |446L9P_ACE_DELETE | L9P_ACE_DELETE_CHILD |447L9P_ACE_WRITE_NAMED_ATTRS | L9P_ACE_WRITE_ATTRIBUTES |448L9P_ACE_WRITE_ACL))449rwx |= 2;450if (opmask & L9P_ACE_EXECUTE)451rwx |= 1;452return (rwx);453}454455#ifndef __APPLE__456/*457* Allocate new ACL holder and ACEs.458*/459static struct l9p_acl *460l9p_new_acl(uint32_t acetype, uint32_t aceasize)461{462struct l9p_acl *ret;463size_t asize, size;464465asize = aceasize * sizeof(struct l9p_ace);466size = sizeof(struct l9p_acl) + asize;467ret = malloc(size);468if (ret != NULL) {469ret->acl_acetype = acetype;470ret->acl_nace = 0;471ret->acl_aceasize = aceasize;472}473return (ret);474}475476/*477* Expand ACL to accomodate more entries.478*479* Currently won't shrink, only grow, so it's a fast no-op until480* we hit the allocated size. After that, it's best to grow in481* big chunks, or this will be O(n**2).482*/483static struct l9p_acl *484l9p_growacl(struct l9p_acl *acl, uint32_t aceasize)485{486struct l9p_acl *tmp;487size_t asize, size;488489if (acl->acl_aceasize < aceasize) {490asize = aceasize * sizeof(struct l9p_ace);491size = sizeof(struct l9p_acl) + asize;492tmp = realloc(acl, size);493if (tmp == NULL)494free(acl);495acl = tmp;496}497return (acl);498}499500/*501* Annoyingly, there's no POSIX-standard way to count the number502* of ACEs in a system ACL other than to walk through them all.503* This is silly, but at least 2n is still O(n), and the walk is504* short. (If the system ACL mysteriously grows, we'll handle505* that OK via growacl(), too.)506*/507static int508l9p_count_aces(acl_t sysacl)509{510acl_entry_t entry;511uint32_t n;512int id;513514id = ACL_FIRST_ENTRY;515for (n = 0; acl_get_entry(sysacl, id, &entry) == 1; n++)516id = ACL_NEXT_ENTRY;517518return ((int)n);519}520521/*522* Create ACL with ACEs from the given acl_t. We use the given523* convert function on each ACE.524*/525static struct l9p_acl *526l9p_sysacl_to_acl(int acetype, acl_t sysacl, econvertfn *convert)527{528struct l9p_acl *acl;529acl_entry_t entry;530uint32_t n;531int error, id;532533acl = l9p_new_acl((uint32_t)acetype, (uint32_t)l9p_count_aces(sysacl));534if (acl == NULL)535return (NULL);536id = ACL_FIRST_ENTRY;537for (n = 0;;) {538if (acl_get_entry(sysacl, id, &entry) != 1)539break;540acl = l9p_growacl(acl, n + 1);541if (acl == NULL)542return (NULL);543error = (*convert)(entry, &acl->acl_aces[n]);544id = ACL_NEXT_ENTRY;545if (error == 0)546n++;547}548acl->acl_nace = n;549return (acl);550}551#endif552553#if defined(HAVE_POSIX_ACLS) && 0 /* not yet */554struct l9p_acl *555l9p_posix_acl_to_acl(acl_t sysacl)556{557}558#endif559560#if defined(HAVE_FREEBSD_ACLS)561static int562l9p_frombsdnfs4(acl_entry_t sysace, struct l9p_ace *ace)563{564acl_tag_t tag; /* e.g., USER_OBJ, GROUP, etc */565acl_entry_type_t entry_type; /* e.g., allow/deny */566acl_permset_t absdperm;567acl_flagset_t absdflag;568acl_perm_t bsdperm; /* e.g., READ_DATA */569acl_flag_t bsdflag; /* e.g., FILE_INHERIT_ACE */570uint32_t flags, mask;571int error;572uid_t uid, *aid;573574error = acl_get_tag_type(sysace, &tag);575if (error == 0)576error = acl_get_entry_type_np(sysace, &entry_type);577if (error == 0)578error = acl_get_flagset_np(sysace, &absdflag);579if (error == 0)580error = acl_get_permset(sysace, &absdperm);581if (error)582return (error);583584flags = 0;585uid = 0;586aid = NULL;587588/* move user/group/everyone + id-is-group-id into flags */589switch (tag) {590case ACL_USER_OBJ:591flags |= L9P_ACEF_OWNER;592break;593case ACL_GROUP_OBJ:594flags |= L9P_ACEF_GROUP;595break;596case ACL_EVERYONE:597flags |= L9P_ACEF_EVERYONE;598break;599case ACL_GROUP:600flags |= L9P_ACEF_IDENTIFIER_GROUP;601/* FALLTHROUGH */602case ACL_USER:603aid = acl_get_qualifier(sysace); /* ugh, this malloc()s */604if (aid == NULL)605return (ENOMEM);606uid = *(uid_t *)aid;607free(aid);608aid = &uid;609break;610default:611return (EINVAL); /* can't happen */612}613614switch (entry_type) {615616case ACL_ENTRY_TYPE_ALLOW:617ace->ace_type = L9P_ACET_ACCESS_ALLOWED;618break;619620case ACL_ENTRY_TYPE_DENY:621ace->ace_type = L9P_ACET_ACCESS_DENIED;622break;623624case ACL_ENTRY_TYPE_AUDIT:625ace->ace_type = L9P_ACET_SYSTEM_AUDIT;626break;627628case ACL_ENTRY_TYPE_ALARM:629ace->ace_type = L9P_ACET_SYSTEM_ALARM;630break;631632default:633return (EINVAL); /* can't happen */634}635636/* transform remaining BSD flags to internal NFS-y form */637bsdflag = *absdflag;638if (bsdflag & ACL_ENTRY_FILE_INHERIT)639flags |= L9P_ACEF_FILE_INHERIT_ACE;640if (bsdflag & ACL_ENTRY_DIRECTORY_INHERIT)641flags |= L9P_ACEF_DIRECTORY_INHERIT_ACE;642if (bsdflag & ACL_ENTRY_NO_PROPAGATE_INHERIT)643flags |= L9P_ACEF_NO_PROPAGATE_INHERIT_ACE;644if (bsdflag & ACL_ENTRY_INHERIT_ONLY)645flags |= L9P_ACEF_INHERIT_ONLY_ACE;646if (bsdflag & ACL_ENTRY_SUCCESSFUL_ACCESS)647flags |= L9P_ACEF_SUCCESSFUL_ACCESS_ACE_FLAG;648if (bsdflag & ACL_ENTRY_FAILED_ACCESS)649flags |= L9P_ACEF_FAILED_ACCESS_ACE_FLAG;650ace->ace_flags = flags;651652/*653* Transform BSD permissions to ace_mask. Note that directory654* vs file bits are the same in both sets, so we don't need655* to worry about that, at least.656*657* There seem to be no BSD equivalents for WRITE_RETENTION658* and WRITE_RETENTION_HOLD.659*/660mask = 0;661bsdperm = *absdperm;662if (bsdperm & ACL_READ_DATA)663mask |= L9P_ACE_READ_DATA;664if (bsdperm & ACL_WRITE_DATA)665mask |= L9P_ACE_WRITE_DATA;666if (bsdperm & ACL_APPEND_DATA)667mask |= L9P_ACE_APPEND_DATA;668if (bsdperm & ACL_READ_NAMED_ATTRS)669mask |= L9P_ACE_READ_NAMED_ATTRS;670if (bsdperm & ACL_WRITE_NAMED_ATTRS)671mask |= L9P_ACE_WRITE_NAMED_ATTRS;672if (bsdperm & ACL_EXECUTE)673mask |= L9P_ACE_EXECUTE;674if (bsdperm & ACL_DELETE_CHILD)675mask |= L9P_ACE_DELETE_CHILD;676if (bsdperm & ACL_READ_ATTRIBUTES)677mask |= L9P_ACE_READ_ATTRIBUTES;678if (bsdperm & ACL_WRITE_ATTRIBUTES)679mask |= L9P_ACE_WRITE_ATTRIBUTES;680/* L9P_ACE_WRITE_RETENTION */681/* L9P_ACE_WRITE_RETENTION_HOLD */682/* 0x00800 */683if (bsdperm & ACL_DELETE)684mask |= L9P_ACE_DELETE;685if (bsdperm & ACL_READ_ACL)686mask |= L9P_ACE_READ_ACL;687if (bsdperm & ACL_WRITE_ACL)688mask |= L9P_ACE_WRITE_ACL;689if (bsdperm & ACL_WRITE_OWNER)690mask |= L9P_ACE_WRITE_OWNER;691if (bsdperm & ACL_SYNCHRONIZE)692mask |= L9P_ACE_SYNCHRONIZE;693ace->ace_mask = mask;694695/* fill in variable-size user or group ID bytes */696if (aid == NULL)697ace->ace_idsize = 0;698else {699ace->ace_idsize = sizeof(uid);700memcpy(&ace->ace_idbytes[0], aid, sizeof(uid));701}702703return (0);704}705706struct l9p_acl *707l9p_freebsd_nfsv4acl_to_acl(acl_t sysacl)708{709710return (l9p_sysacl_to_acl(L9P_ACLTYPE_NFSv4, sysacl, l9p_frombsdnfs4));711}712#endif713714#if defined(HAVE_DARWIN_ACLS) && 0 /* not yet */715struct l9p_acl *716l9p_darwin_nfsv4acl_to_acl(acl_t sysacl)717{718}719#endif720721722