From 11462f7fb7a0216118ae88a2b3f38adf7204765b Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 24 May 2021 10:49:43 -0400 Subject: [PATCH 1/5] Implement NFSv41 ACLs through xattr This implements NFSv41 (RFC 5661) ACLs in a manner compatible with vfs_nfs4acl_xattr in Samba. There are three key areas of change in this commit: 1) NFSv4 ACL management through system.nfs4_acl_xdr xattr. Install an xattr handler for "system.nfs4_acl_xdr" that presents an xattr containing full NFSv41 ACL structures generated through rpcgen using specification from the Samba project. This xattr is used by userspace programs to read and set permissions. 2) add an i_op->permissions endpoint: zpl_permissions(). This is used by the VFS in Linux to determine whether to allow / deny an operation. Wherever possible, we try to avoid having to call zfs_access(). If kernel has NFSv4 patch for VFS, then perform more complete check of avaiable access mask. 3) add capability-based overrides to secpolicy_vnode_access2(). There are various situations in which ACL may need to be overridden based on capabilities. This logic is almost directly copied from Linux VFS. Switch to using ns-aware checks rather than capable(). Expand optimization allow bypass of zfs_zaccess() in case of trivial ACL if MAY_OPEN is included in requested mask. This is commit was initially inspired by work from Paul B. Henson to implement NFSv4.0 (RFC3530) ACLs in ZFS on Linux. Key areas of divergence are as follows: - ACL specification, xattr format, xattr name - Addition of handling for NFSv4 masks from Linux VFS - Addition of ACL overrides based on capabilities Authored-by: Andrew Walker Signed-off-by: Umer Saleem --- include/os/linux/spl/rpc/xdr.h | 1 + include/os/linux/zfs/sys/zpl.h | 9 + module/os/linux/zfs/policy.c | 48 +++- module/os/linux/zfs/zfs_vfsops.c | 14 +- module/os/linux/zfs/zpl_inode.c | 3 + module/os/linux/zfs/zpl_super.c | 3 + module/os/linux/zfs/zpl_xattr.c | 428 +++++++++++++++++++++++++++++++ 7 files changed, 503 insertions(+), 3 deletions(-) diff --git a/include/os/linux/spl/rpc/xdr.h b/include/os/linux/spl/rpc/xdr.h index 5b621fa9c863..606566113e1c 100644 --- a/include/os/linux/spl/rpc/xdr.h +++ b/include/os/linux/spl/rpc/xdr.h @@ -22,6 +22,7 @@ #define _SPL_RPC_XDR_H #include +#include /* * XDR enums and types. diff --git a/include/os/linux/zfs/sys/zpl.h b/include/os/linux/zfs/sys/zpl.h index c6e235c48ef3..ce7e50c13bed 100644 --- a/include/os/linux/zfs/sys/zpl.h +++ b/include/os/linux/zfs/sys/zpl.h @@ -98,6 +98,15 @@ zpl_chmod_acl(struct inode *ip) } #endif /* CONFIG_FS_POSIX_ACL */ +#if defined(HAVE_IOPS_PERMISSION_USERNS) +extern int zpl_permission(struct user_namespace *userns, struct inode *ip, + int mask); +#elif defined(HAVE_IOPS_PERMISSION_IDMAP) +extern int zpl_permission(struct mnt_idmap *idmap, struct inode *ip, int mask); +#else +extern int zpl_permission(struct inode *ip, int mask); +#endif + extern xattr_handler_t *zpl_xattr_handlers[]; /* zpl_ctldir.c */ diff --git a/module/os/linux/zfs/policy.c b/module/os/linux/zfs/policy.c index d21bc667ba69..a7b5a7b3e66d 100644 --- a/module/os/linux/zfs/policy.c +++ b/module/os/linux/zfs/policy.c @@ -33,6 +33,7 @@ #include #include #include +#include /* * The passed credentials cannot be directly verified because Linux only @@ -103,13 +104,56 @@ secpolicy_sys_config(const cred_t *cr, boolean_t checkonly) * Like secpolicy_vnode_access() but we get the actual wanted mode and the * current mode of the file, not the missing bits. * - * Enforced in the Linux VFS. + * If filesystem is using NFSv4 ACLs, validate the current mode + * and the wanted mode are the same, otherwise access fails. + * + * If using POSIX ACLs or no ACLs, enforced in the Linux VFS. */ int secpolicy_vnode_access2(const cred_t *cr, struct inode *ip, uid_t owner, mode_t curmode, mode_t wantmode) { - return (0); + mode_t remainder = ~curmode & wantmode; + uid_t uid = crgetuid(cr); + if ((ITOZSB(ip)->z_acl_type != ZFS_ACLTYPE_NFSV4) || + (remainder == 0)) { + return (0); + } + + if (uid == 0) + return (0); + +#if defined(CONFIG_USER_NS) + if (!kuid_has_mapping(cr->user_ns, SUID_TO_KUID(owner))) + return (EPERM); +#endif + + /* + * There are some situations in which capabilities + * may allow overriding the DACL. + */ + if (S_ISDIR(ip->i_mode)) { + if (!(wantmode & S_IWUSR) && + (priv_policy_user(cr, CAP_DAC_READ_SEARCH, EPERM) == 0)) { + return (0); + } + if (priv_policy_user(cr, CAP_DAC_OVERRIDE, EPERM) == 0) { + return (0); + } + return (EACCES); + } + + if ((wantmode == S_IRUSR) && + (priv_policy_user(cr, CAP_DAC_READ_SEARCH, EPERM) == 0)) { + return (0); + } + + if (!(remainder & S_IXUSR) && + (priv_policy_user(cr, CAP_DAC_OVERRIDE, EPERM) == 0)) { + return (0); + } + + return (EACCES); } /* diff --git a/module/os/linux/zfs/zfs_vfsops.c b/module/os/linux/zfs/zfs_vfsops.c index b226fca147a5..8a1456d4bef5 100644 --- a/module/os/linux/zfs/zfs_vfsops.c +++ b/module/os/linux/zfs/zfs_vfsops.c @@ -357,12 +357,17 @@ acltype_changed_cb(void *arg, uint64_t newval) zfsvfs_t *zfsvfs = arg; switch (newval) { - case ZFS_ACLTYPE_NFSV4: case ZFS_ACLTYPE_OFF: zfsvfs->z_acl_type = ZFS_ACLTYPE_OFF; zfsvfs->z_sb->s_flags &= ~SB_POSIXACL; +#ifdef SB_NFSV4ACL + zfsvfs->z_sb->s_flags &= ~SB_NFSV4ACL; +#endif break; case ZFS_ACLTYPE_POSIX: +#ifdef SB_NFSV4ACL + zfsvfs->z_sb->s_flags &= ~SB_NFSV4ACL; +#endif #ifdef CONFIG_FS_POSIX_ACL zfsvfs->z_acl_type = ZFS_ACLTYPE_POSIX; zfsvfs->z_sb->s_flags |= SB_POSIXACL; @@ -371,6 +376,13 @@ acltype_changed_cb(void *arg, uint64_t newval) zfsvfs->z_sb->s_flags &= ~SB_POSIXACL; #endif /* CONFIG_FS_POSIX_ACL */ break; + case ZFS_ACLTYPE_NFSV4: + zfsvfs->z_acl_type = ZFS_ACLTYPE_NFSV4; + zfsvfs->z_sb->s_flags &= ~SB_POSIXACL; +#ifdef SB_NFSV4ACL + zfsvfs->z_sb->s_flags |= SB_NFSV4ACL; +#endif + break; default: break; } diff --git a/module/os/linux/zfs/zpl_inode.c b/module/os/linux/zfs/zpl_inode.c index c4b5087ca5e7..2c90953f8a96 100644 --- a/module/os/linux/zfs/zpl_inode.c +++ b/module/os/linux/zfs/zpl_inode.c @@ -797,6 +797,7 @@ const struct inode_operations zpl_inode_operations = { .get_acl = zpl_get_acl, #endif /* HAVE_GET_INODE_ACL */ #endif /* CONFIG_FS_POSIX_ACL */ + .permission = zpl_permission, }; const struct inode_operations zpl_dir_inode_operations = { @@ -827,6 +828,7 @@ const struct inode_operations zpl_dir_inode_operations = { .get_acl = zpl_get_acl, #endif /* HAVE_GET_INODE_ACL */ #endif /* CONFIG_FS_POSIX_ACL */ + .permission = zpl_permission, }; const struct inode_operations zpl_symlink_inode_operations = { @@ -848,4 +850,5 @@ const struct inode_operations zpl_special_inode_operations = { .get_acl = zpl_get_acl, #endif /* HAVE_GET_INODE_ACL */ #endif /* CONFIG_FS_POSIX_ACL */ + .permission = zpl_permission, }; diff --git a/module/os/linux/zfs/zpl_super.c b/module/os/linux/zfs/zpl_super.c index b97b701b7460..bf820eb600b7 100644 --- a/module/os/linux/zfs/zpl_super.c +++ b/module/os/linux/zfs/zpl_super.c @@ -219,6 +219,9 @@ __zpl_show_options(struct seq_file *seq, zfsvfs_t *zfsvfs) case ZFS_ACLTYPE_POSIX: seq_puts(seq, ",posixacl"); break; + case ZFS_ACLTYPE_NFSV4: + seq_puts(seq, ",nfs4acl"); + break; default: seq_puts(seq, ",noacl"); break; diff --git a/module/os/linux/zfs/zpl_xattr.c b/module/os/linux/zfs/zpl_xattr.c index ebf0afc9eb02..7779ad82345f 100644 --- a/module/os/linux/zfs/zpl_xattr.c +++ b/module/os/linux/zfs/zpl_xattr.c @@ -80,11 +80,34 @@ #include #include #include +#include #include #include #include #include +#define NFS41ACL_XATTR "system.nfs4_acl_xdr" + +static const struct { + int kmask; + int zfsperm; +} mask2zfs[] = { + { MAY_READ, ACE_READ_DATA }, + { MAY_WRITE, ACE_WRITE_DATA }, + { MAY_EXEC, ACE_EXECUTE }, +#ifdef SB_NFSV4ACL + { MAY_DELETE, ACE_DELETE }, + { MAY_DELETE_CHILD, ACE_DELETE_CHILD }, + { MAY_WRITE_ATTRS, ACE_WRITE_ATTRIBUTES }, + { MAY_WRITE_NAMED_ATTRS, ACE_WRITE_NAMED_ATTRS }, + { MAY_WRITE_ACL, ACE_WRITE_ACL }, + { MAY_WRITE_OWNER, ACE_WRITE_OWNER }, +#endif +}; + +#define POSIX_MASKS (MAY_READ|MAY_WRITE|MAY_EXEC|MAY_OPEN) +#define GENERIC_MASK(mask) ((mask & ~POSIX_MASKS) == 0) + enum xattr_permission { XAPERM_DENY, XAPERM_ALLOW, @@ -1384,6 +1407,406 @@ static xattr_handler_t zpl_xattr_acl_default_handler = { #endif /* CONFIG_FS_POSIX_ACL */ +int +#if defined(HAVE_IOPS_PERMISSION_USERNS) +zpl_permission(struct user_namespace *userns, struct inode *ip, int mask) +#elif defined(HAVE_IOPS_PERMISSION_IDMAP) +zpl_permission(struct mnt_idmap *idmap, struct inode *ip, int mask) +#else +zpl_permission(struct inode *ip, int mask) +#endif +{ + int to_check = 0, i, ret; + cred_t *cr = NULL; + + /* + * If NFSv4 ACLs are not being used, go back to + * generic_permission(). If ACL is trivial and the + * mask is representable by POSIX permissions, then + * also go back to generic_permission(). + */ + if ((ITOZSB(ip)->z_acl_type != ZFS_ACLTYPE_NFSV4) || + ((ITOZ(ip)->z_pflags & ZFS_ACL_TRIVIAL && GENERIC_MASK(mask)))) { +#if (defined(HAVE_IOPS_PERMISSION_USERNS) || \ + defined(HAVE_IOPS_PERMISSION_IDMAP)) + return (generic_permission(zfs_init_idmap, ip, mask)); +#else + return (generic_permission(ip, mask)); +#endif + } + + for (i = 0; i < ARRAY_SIZE(mask2zfs); i++) { + if (mask & mask2zfs[i].kmask) { + to_check |= mask2zfs[i].zfsperm; + } + } + + /* + * We're being asked to check something that doesn't contain an + * NFSv4 ACE. Pass back to default kernel permissions check. + */ + if (to_check == 0) { +#if (defined(HAVE_IOPS_PERMISSION_USERNS) || \ + defined(HAVE_IOPS_PERMISSION_IDMAP)) + return (generic_permission(zfs_init_idmap, ip, mask)); +#else + return (generic_permission(ip, mask)); +#endif + } + + /* + * Avoid potentially blocking in RCU walk. + */ + if (mask & MAY_NOT_BLOCK) { + return (-ECHILD); + } + + cr = CRED(); + crhold(cr); + ret = -zfs_access(ITOZ(ip), to_check, V_ACE_MASK, cr); + if (ret != -EPERM && ret != -EACCES) { + crfree(cr); + return (ret); + } + + /* + * There are some situations in which capabilities + * may allow overriding the DACL. + */ + if (S_ISDIR(ip->i_mode)) { +#ifdef SB_NFSV4ACL + if (!(mask & (MAY_WRITE | NFS41ACL_WRITE_ALL))) { +#else + if (!(mask & MAY_WRITE)) { +#endif + if (capable(CAP_DAC_READ_SEARCH)) { + crfree(cr); + return (0); + } + } + if (capable(CAP_DAC_OVERRIDE)) { + crfree(cr); + return (0); + } + crfree(cr); + return (ret); + } + + if (to_check == ACE_READ_DATA) { + if (capable(CAP_DAC_READ_SEARCH)) { + crfree(cr); + return (0); + } + } + + if (!(mask & MAY_EXEC) || + (zfs_fastaccesschk_execute(ITOZ(ip), cr) == 0)) { + if (capable(CAP_DAC_OVERRIDE)) { + crfree(cr); + return (0); + } + } + + crfree(cr); + return (ret); +} + +#define ACEI4_SPECIAL_WHO 1 +#define ACE4_SPECIAL_OWNER 1 +#define ACE4_SPECIAL_GROUP 2 +#define ACE4_SPECIAL_EVERYONE 3 +#define NFS41ACL_MAX_ACES 128 +#define NFS41_FLAGS (ACE_DIRECTORY_INHERIT_ACE| \ + ACE_FILE_INHERIT_ACE| \ + ACE_NO_PROPAGATE_INHERIT_ACE| \ + ACE_INHERIT_ONLY_ACE| \ + ACE_INHERITED_ACE| \ + ACE_IDENTIFIER_GROUP) + +/* + * Macros for sanity checks related to XDR and ACL buffer sizes + */ +#define ACE4ELEM 5 +#define ACE4SIZE (ACE4ELEM * sizeof (u32)) +#define XDRBASE (2 * sizeof (u32)) + +#define ACES_TO_XDRSIZE(x) (XDRBASE + ((x) * ACE4SIZE)) +#define XDRSIZE_TO_ACES(x) (((x) - XDRBASE) / ACE4SIZE) +#define XDRSIZE_IS_VALID(x) (((x) >= XDRBASE) && \ + ((((x) - XDRBASE) % ACE4SIZE) == 0)) + +static int +__zpl_xattr_nfs41acl_list(struct inode *ip, char *list, size_t list_size, + const char *name, size_t name_len) +{ + char *xattr_name = NFS41ACL_XATTR; + size_t xattr_size = sizeof (NFS41ACL_XATTR); + + if (ITOZSB(ip)->z_acl_type != ZFS_ACLTYPE_NFSV4) + return (0); + + if (list && xattr_size <= list_size) + memcpy(list, xattr_name, xattr_size); + + return (xattr_size); +} +ZPL_XATTR_LIST_WRAPPER(zpl_xattr_nfs41acl_list); + +static int +acep_to_nfsace4i(const ace_t *acep, u32 *xattrbuf) +{ + u32 who = 0, iflag = 0; + + switch (acep->a_flags & ACE_TYPE_FLAGS) { + case ACE_OWNER: + iflag = ACEI4_SPECIAL_WHO; + who = ACE4_SPECIAL_OWNER; + break; + + case ACE_GROUP|ACE_IDENTIFIER_GROUP: + iflag = ACEI4_SPECIAL_WHO; + who = ACE4_SPECIAL_GROUP; + break; + + case ACE_EVERYONE: + iflag = ACEI4_SPECIAL_WHO; + who = ACE4_SPECIAL_EVERYONE; + break; + + case ACE_IDENTIFIER_GROUP: + case 0: + who = acep->a_who; + break; + + default: + dprintf("Unknown ACE_TYPE_FLAG 0x%08x\n", + acep->a_flags & ACE_TYPE_FLAGS); + return (-EINVAL); + } + + *xattrbuf++ = htonl(acep->a_type); + *xattrbuf++ = htonl(acep->a_flags & NFS41_FLAGS); + *xattrbuf++ = htonl(iflag); + *xattrbuf++ = htonl(acep->a_access_mask); + *xattrbuf++ = htonl(who); + + return (0); +} + +static int +zfsacl_to_nfsacl41i(const vsecattr_t vsecp, u32 *xattrbuf) +{ + int i, error = 0; + ace_t *acep = NULL; + + *xattrbuf++ = htonl(vsecp.vsa_aclflags); + *xattrbuf++ = htonl(vsecp.vsa_aclcnt); + + for (i = 0; i < vsecp.vsa_aclcnt; i++, xattrbuf += ACE4ELEM) { + acep = vsecp.vsa_aclentp + (i * sizeof (ace_t)); + + error = acep_to_nfsace4i(acep, xattrbuf); + if (error) + break; + } + + return (error); +} + +static int +nfsace4i_to_acep(const u32 *xattrbuf, ace_t *acep) +{ + u32 iflag, id; + + acep->a_type = ntohl(*(xattrbuf++)); + acep->a_flags = ntohl(*(xattrbuf++)) & NFS41_FLAGS; + iflag = ntohl(*(xattrbuf++)); + acep->a_access_mask = ntohl(*(xattrbuf++)); + id = ntohl(*(xattrbuf++)); + + if (iflag & ACEI4_SPECIAL_WHO) { + switch (id) { + case ACE4_SPECIAL_OWNER: + acep->a_flags |= ACE_OWNER; + acep->a_who = -1; + break; + + case ACE4_SPECIAL_GROUP: + acep->a_flags |= (ACE_GROUP | ACE_IDENTIFIER_GROUP); + acep->a_who = -1; + break; + + case ACE4_SPECIAL_EVERYONE: + acep->a_flags |= ACE_EVERYONE; + acep->a_who = -1; + break; + + default: + dprintf("Unknown id 0x%08x\n", id); + return (-EINVAL); + } + } else { + acep->a_who = id; + } + + return (0); +} + +static int +nfsacl41i_to_zfsacl(const u32 *xattrbuf, size_t bufsz, vsecattr_t *vsecp) +{ + int error; + int i; + + vsecp->vsa_aclflags = ntohl(*(xattrbuf++)); + vsecp->vsa_aclcnt = ntohl(*(xattrbuf++)); + bufsz -= (2 * sizeof (u32)); + vsecp->vsa_aclentsz = vsecp->vsa_aclcnt * sizeof (ace_t); + + if (bufsz != (vsecp->vsa_aclcnt * ACE4SIZE)) { + dprintf("Embedded ACL count [%d] is not equal to " + "what can fit in provided buffer size: %zu\n", + vsecp->vsa_aclcnt, bufsz); + return (-ERANGE); + } + + vsecp->vsa_aclentp = kmem_alloc(vsecp->vsa_aclentsz, KM_SLEEP); + + for (i = 0; i < vsecp->vsa_aclcnt; i++, xattrbuf += ACE4ELEM) { + ace_t *acep = vsecp->vsa_aclentp + (i * sizeof (ace_t)); + + error = nfsace4i_to_acep(xattrbuf, acep); + if (error) { + kmem_free(vsecp->vsa_aclentp, vsecp->vsa_aclentsz); + return (error); + } + } + + return (0); +} + +static int +__zpl_xattr_nfs41acl_get(struct inode *ip, const char *name, + void *buffer, size_t size) +{ + vsecattr_t vsecp = {0}; + cred_t *cr = CRED(); + int ret, fl; + size_t xdr_size; + + if (ITOZSB(ip)->z_acl_type != ZFS_ACLTYPE_NFSV4) + return (-EOPNOTSUPP); + + if (size == 0) { + /* + * API user may send 0 size so that we + * return size of buffer needed for ACL. + */ + crhold(cr); + vsecp.vsa_mask = VSA_ACECNT; + ret = -zfs_getsecattr(ITOZ(ip), &vsecp, ATTR_NOACLCHECK, cr); + if (ret) { + return (ret); + } + crfree(cr); + ret = ACES_TO_XDRSIZE(vsecp.vsa_aclcnt); + return (ret); + } + + vsecp.vsa_mask = VSA_ACE_ALLTYPES | VSA_ACECNT | VSA_ACE | + VSA_ACE_ACLFLAGS; + + crhold(cr); + fl = capable(CAP_DAC_OVERRIDE) ? ATTR_NOACLCHECK : 0; + ret = -zfs_getsecattr(ITOZ(ip), &vsecp, fl, cr); + crfree(cr); + + if (ret) { + return (ret); + } + + if (vsecp.vsa_aclcnt == 0) { + ret = -ENODATA; + goto nfs4acl_get_out; + } + + xdr_size = ACES_TO_XDRSIZE(vsecp.vsa_aclcnt); + if (xdr_size > size) { + ret = -ERANGE; + goto nfs4acl_get_out; + } + + ret = zfsacl_to_nfsacl41i(vsecp, (u32 *)buffer); + if (ret == 0) + ret = xdr_size; + +nfs4acl_get_out: + kmem_free(vsecp.vsa_aclentp, vsecp.vsa_aclentsz); + + return (ret); +} +ZPL_XATTR_GET_WRAPPER(zpl_xattr_nfs41acl_get); + +static int +__zpl_xattr_nfs41acl_set(struct inode *ip, const char *name, + const void *value, size_t size, int flags) +{ + cred_t *cr = CRED(); + int error, fl, naces; + vsecattr_t vsecp = { .vsa_mask = (VSA_ACE | VSA_ACE_ACLFLAGS) }; + + if (ITOZSB(ip)->z_acl_type != ZFS_ACLTYPE_NFSV4) + return (-EOPNOTSUPP); + + /* + * TODO: we may receive NULL value and size 0 + * when rmxattr() on our special xattr is called. + * A function to "strip" the ACL needs to be added + * to avoid POLA violation. + */ + + /* xdr data is 4-byte aligned */ + if (((ulong_t)value % 4) != 0) { + return (-EINVAL); + } + + naces = XDRSIZE_TO_ACES(size); + if (naces > NFS41ACL_MAX_ACES) { + return (-E2BIG); + } + + if (!XDRSIZE_IS_VALID(size)) { + return (-EINVAL); + } + + error = nfsacl41i_to_zfsacl((u32 *)value, size, &vsecp); + if (error) + return (error); + + crhold(cr); + fl = capable(CAP_DAC_OVERRIDE) ? ATTR_NOACLCHECK : 0; + error = -zfs_setsecattr(ITOZ(ip), &vsecp, fl, cr); + crfree(cr); + + kmem_free(vsecp.vsa_aclentp, vsecp.vsa_aclentsz); + return (error); +} +ZPL_XATTR_SET_WRAPPER(zpl_xattr_nfs41acl_set); + +/* + * ACL access xattr namespace handlers. + * + * Use .name instead of .prefix when available. xattr_resolve_name will match + * whole name and reject anything that has .name only as prefix. + */ +xattr_handler_t zpl_xattr_nfs41acl_handler = +{ + .name = NFS41ACL_XATTR, + .list = zpl_xattr_nfs41acl_list, + .get = zpl_xattr_nfs41acl_get, + .set = zpl_xattr_nfs41acl_set, +}; + xattr_handler_t *zpl_xattr_handlers[] = { &zpl_xattr_security_handler, &zpl_xattr_trusted_handler, @@ -1392,6 +1815,7 @@ xattr_handler_t *zpl_xattr_handlers[] = { &zpl_xattr_acl_access_handler, &zpl_xattr_acl_default_handler, #endif /* CONFIG_FS_POSIX_ACL */ + &zpl_xattr_nfs41acl_handler, NULL }; @@ -1420,6 +1844,10 @@ zpl_xattr_handler(const char *name) return (&zpl_xattr_acl_default_handler); #endif /* CONFIG_FS_POSIX_ACL */ + if (strncmp(name, NFS41ACL_XATTR, + sizeof (NFS41ACL_XATTR)) == 0) + return (&zpl_xattr_nfs41acl_handler); + return (NULL); } From 7665b76dcb41b4276c265a8fef4f2760f68bd654 Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 21 Jun 2022 13:48:30 -0500 Subject: [PATCH 2/5] Simplify and enhance NFSv4.1 ACLs Adds ability for xattr handler to "strip" NFSv4.1 ACLs. Since there is no libc equivalent of strip operation in Linux for NFSv4 ACLs, as there are in POSIX ACLs and on FreeBSD, this commit handles the operation entirely in ZFS. Expose ACL_IS_TRIVIAL and ACL_IS_DIR flags as ACL-wide flags in the system.nfs4_acl_xdr generated on getxattr requests. This are non-RFC flags that are useful for userspace applications. ACL_IS_TRIVIAL helps to avoid relatively expensive ACL-related operations. Advertise support for large xattrs. SB_LARGEXATTR is used to indicate to the kernel that the filesystem supports large-size xattrs greater than 64KiB. This flag is used to evaluate whether to allow large xattr read or write requests (up to 2 MiB). Force BSD semantics for group ownership if NFSV4ACL. Since there is no hard-and-fast rule about creation semantics for NFSv4 ACLs on Linux, opt for what is least likely to break users permissions on change from FreeBSD to Linux. Improves zpl_permission performance. This function can be frequently called with MAY_EXEC|MAY_NOT_BLOCK during RCU path walk. Authored-by: Andrew Walker Signed-off-by: Umer Saleem --- include/os/linux/spl/sys/acl.h | 2 + include/sys/zfs_acl.h | 2 + module/os/freebsd/zfs/zfs_acl.c | 6 + module/os/linux/zfs/zfs_acl.c | 173 +++++++++++++++++++++++------ module/os/linux/zfs/zfs_vfsops.c | 6 + module/os/linux/zfs/zfs_vnops_os.c | 37 +++++- module/os/linux/zfs/zpl_xattr.c | 127 +++++++++++++++------ 7 files changed, 277 insertions(+), 76 deletions(-) diff --git a/include/os/linux/spl/sys/acl.h b/include/os/linux/spl/sys/acl.h index 5cd7a56b86ec..d66b523b985e 100644 --- a/include/os/linux/spl/sys/acl.h +++ b/include/os/linux/spl/sys/acl.h @@ -83,6 +83,8 @@ typedef struct ace_object { #define ACL_PROTECTED 0x0002 #define ACL_DEFAULTED 0x0004 #define ACL_FLAGS_ALL (ACL_AUTO_INHERIT|ACL_PROTECTED|ACL_DEFAULTED) +#define ACL_IS_TRIVIAL 0x10000 +#define ACL_IS_DIR 0x20000 #define ACE_ACCESS_ALLOWED_COMPOUND_ACE_TYPE 0x04 #define ACE_ACCESS_ALLOWED_OBJECT_ACE_TYPE 0x05 diff --git a/include/sys/zfs_acl.h b/include/sys/zfs_acl.h index e19288528849..a137393548d8 100644 --- a/include/sys/zfs_acl.h +++ b/include/sys/zfs_acl.h @@ -211,6 +211,8 @@ void zfs_acl_ids_free(zfs_acl_ids_t *); boolean_t zfs_acl_ids_overquota(struct zfsvfs *, zfs_acl_ids_t *, uint64_t); int zfs_getacl(struct znode *, vsecattr_t *, boolean_t, cred_t *); int zfs_setacl(struct znode *, vsecattr_t *, boolean_t, cred_t *); +int zfs_stripacl(struct znode *, cred_t *); + void zfs_acl_rele(void *); void zfs_oldace_byteswap(ace_t *, int); void zfs_ace_byteswap(void *, size_t, boolean_t); diff --git a/module/os/freebsd/zfs/zfs_acl.c b/module/os/freebsd/zfs/zfs_acl.c index 1f1ac3a38d7a..3ef0f797f362 100644 --- a/module/os/freebsd/zfs/zfs_acl.c +++ b/module/os/freebsd/zfs/zfs_acl.c @@ -2029,6 +2029,12 @@ zfs_setacl(znode_t *zp, vsecattr_t *vsecp, boolean_t skipaclchk, cred_t *cr) return (error); } +int +zfs_stripacl(znode_t *zp, cred_t *cr) +{ + return (SET_ERROR(EOPNOTSUPP)); +} + /* * Check accesses of interest (AoI) against attributes of the dataset * such as read-only. Returns zero if no AoI conflict with dataset diff --git a/module/os/linux/zfs/zfs_acl.c b/module/os/linux/zfs/zfs_acl.c index 206bc2209596..9a9eaf1067f2 100644 --- a/module/os/linux/zfs/zfs_acl.c +++ b/module/os/linux/zfs/zfs_acl.c @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -1963,8 +1964,8 @@ zfs_acl_ids_overquota(zfsvfs_t *zv, zfs_acl_ids_t *acl_ids, uint64_t projid) /* * Retrieve a file's ACL */ -int -zfs_getacl(znode_t *zp, vsecattr_t *vsecp, boolean_t skipaclchk, cred_t *cr) +static int +zfs_getacl_impl(znode_t *zp, vsecattr_t *vsecp, boolean_t stripped, cred_t *cr) { zfs_acl_t *aclp; ulong_t mask; @@ -1975,19 +1976,16 @@ zfs_getacl(znode_t *zp, vsecattr_t *vsecp, boolean_t skipaclchk, cred_t *cr) mask = vsecp->vsa_mask & (VSA_ACE | VSA_ACECNT | VSA_ACE_ACLFLAGS | VSA_ACE_ALLTYPES); - if (mask == 0) - return (SET_ERROR(ENOSYS)); - - if ((error = zfs_zaccess(zp, ACE_READ_ACL, 0, skipaclchk, cr, - zfs_init_idmap))) - return (error); - - mutex_enter(&zp->z_acl_lock); - - error = zfs_acl_node_read(zp, B_FALSE, &aclp, B_FALSE); - if (error != 0) { - mutex_exit(&zp->z_acl_lock); - return (error); + if (stripped) { + mode_t mode = ZTOI(zp)->i_mode; + aclp = zfs_acl_alloc(zfs_acl_version_zp(zp)); + (aclp)->z_hints = zp->z_pflags & V4_ACL_WIDE_FLAGS; + zfs_acl_chmod(S_ISDIR(mode), mode, B_TRUE, + (ZTOZSB(zp)->z_acl_mode == ZFS_ACL_GROUPMASK), aclp); + } else { + error = zfs_acl_node_read(zp, B_FALSE, &aclp, B_FALSE); + if (error != 0) + return (error); } /* @@ -2054,11 +2052,37 @@ zfs_getacl(znode_t *zp, vsecattr_t *vsecp, boolean_t skipaclchk, cred_t *cr) vsecp->vsa_aclflags |= ACL_PROTECTED; if (zp->z_pflags & ZFS_ACL_AUTO_INHERIT) vsecp->vsa_aclflags |= ACL_AUTO_INHERIT; + if (zp->z_pflags & ZFS_ACL_TRIVIAL) + vsecp->vsa_aclflags |= ACL_IS_TRIVIAL; + if (S_ISDIR(ZTOI(zp)->i_mode)) + vsecp->vsa_aclflags |= ACL_IS_DIR; + } + + return (0); +} + +int +zfs_getacl(znode_t *zp, vsecattr_t *vsecp, boolean_t skipaclchk, cred_t *cr) +{ + int error; + ulong_t mask; + + mask = vsecp->vsa_mask & (VSA_ACE | VSA_ACECNT | + VSA_ACE_ACLFLAGS | VSA_ACE_ALLTYPES); + + if (mask == 0) + return (SET_ERROR(ENOSYS)); + + if ((error = zfs_zaccess(zp, ACE_READ_ACL, 0, skipaclchk, cr, + zfs_init_idmap))) { + return (error); } + mutex_enter(&zp->z_acl_lock); + error = zfs_getacl_impl(zp, vsecp, B_FALSE, cr); mutex_exit(&zp->z_acl_lock); - return (0); + return (error); } int @@ -2119,12 +2143,11 @@ zfs_vsec_2_aclp(zfsvfs_t *zfsvfs, umode_t obj_mode, /* * Set a file's ACL */ -int -zfs_setacl(znode_t *zp, vsecattr_t *vsecp, boolean_t skipaclchk, cred_t *cr) +static int +zfs_setacl_impl(znode_t *zp, vsecattr_t *vsecp, cred_t *cr) { zfsvfs_t *zfsvfs = ZTOZSB(zp); zilog_t *zilog = zfsvfs->z_log; - ulong_t mask = vsecp->vsa_mask & (VSA_ACE | VSA_ACECNT); dmu_tx_t *tx; int error; zfs_acl_t *aclp; @@ -2132,16 +2155,6 @@ zfs_setacl(znode_t *zp, vsecattr_t *vsecp, boolean_t skipaclchk, cred_t *cr) boolean_t fuid_dirtied; uint64_t acl_obj; - if (mask == 0) - return (SET_ERROR(ENOSYS)); - - if (zp->z_pflags & ZFS_IMMUTABLE) - return (SET_ERROR(EPERM)); - - if ((error = zfs_zaccess(zp, ACE_WRITE_ACL, 0, skipaclchk, cr, - zfs_init_idmap))) - return (error); - error = zfs_vsec_2_aclp(zfsvfs, ZTOI(zp)->i_mode, vsecp, cr, &fuidp, &aclp); if (error) @@ -2156,9 +2169,6 @@ zfs_setacl(znode_t *zp, vsecattr_t *vsecp, boolean_t skipaclchk, cred_t *cr) (zp->z_pflags & V4_ACL_WIDE_FLAGS); } top: - mutex_enter(&zp->z_acl_lock); - mutex_enter(&zp->z_lock); - tx = dmu_tx_create(zfsvfs->z_os); dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_TRUE); @@ -2189,12 +2199,15 @@ zfs_setacl(znode_t *zp, vsecattr_t *vsecp, boolean_t skipaclchk, cred_t *cr) zfs_sa_upgrade_txholds(tx, zp); error = dmu_tx_assign(tx, TXG_NOWAIT); if (error) { - mutex_exit(&zp->z_acl_lock); - mutex_exit(&zp->z_lock); - if (error == ERESTART) { + mutex_exit(&zp->z_acl_lock); + mutex_exit(&zp->z_lock); + dmu_tx_wait(tx); dmu_tx_abort(tx); + + mutex_enter(&zp->z_acl_lock); + mutex_enter(&zp->z_lock); goto top; } dmu_tx_abort(tx); @@ -2216,9 +2229,90 @@ zfs_setacl(znode_t *zp, vsecattr_t *vsecp, boolean_t skipaclchk, cred_t *cr) zfs_fuid_info_free(fuidp); dmu_tx_commit(tx); + return (error); +} + +int +zfs_setacl(znode_t *zp, vsecattr_t *vsecp, boolean_t skipaclchk, cred_t *cr) +{ + ulong_t mask = vsecp->vsa_mask & (VSA_ACE | VSA_ACECNT); + int error; + + if (mask == 0) + return (SET_ERROR(ENOSYS)); + + if (zp->z_pflags & ZFS_IMMUTABLE) + return (SET_ERROR(EPERM)); + + if ((error = zfs_zaccess(zp, ACE_WRITE_ACL, 0, skipaclchk, cr, + zfs_init_idmap))) { + return (error); + } + + mutex_enter(&zp->z_acl_lock); + mutex_enter(&zp->z_lock); + + error = zfs_setacl_impl(zp, vsecp, cr); + + mutex_exit(&zp->z_lock); + mutex_exit(&zp->z_acl_lock); + return (error); +} + + +int +zfs_stripacl(znode_t *zp, cred_t *cr) +{ + int error; + zfsvfs_t *zfsvfs = ZTOZSB(zp); + + vsecattr_t vsec = { + .vsa_mask = VSA_ACE_ALLTYPES | VSA_ACECNT | VSA_ACE | + VSA_ACE_ACLFLAGS + }; + + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + return (error); + + if ((error = zfs_verify_zp(zp)) != 0) + goto done; + + if (zp->z_pflags & ZFS_IMMUTABLE) { + error = SET_ERROR(EPERM); + goto done; + } + + if ((error = zfs_zaccess(zp, ACE_WRITE_ACL, 0, B_FALSE, cr, + zfs_init_idmap))) + goto done; + + if (zp->z_pflags & ZFS_ACL_TRIVIAL) { + // ACL is already stripped. Nothing to do. + error = 0; + goto done; + } + + mutex_enter(&zp->z_acl_lock); + error = zfs_getacl_impl(zp, &vsec, B_TRUE, cr); + if (error) { + mutex_exit(&zp->z_acl_lock); + goto done; + } + + mutex_enter(&zp->z_lock); + + error = zfs_setacl_impl(zp, &vsec, cr); + mutex_exit(&zp->z_lock); mutex_exit(&zp->z_acl_lock); + kmem_free(vsec.vsa_aclentp, vsec.vsa_aclentsz); + + if (zfsvfs->z_os->os_sync == ZFS_SYNC_ALWAYS) + zil_commit(zfsvfs->z_log, 0); + +done: + zfs_exit(zfsvfs, FTAG); return (error); } @@ -2345,8 +2439,11 @@ zfs_zaccess_aces_check(znode_t *zp, uint32_t *working_mode, break; case OWNING_GROUP: who = gowner; - zfs_fallthrough; + checkit = zfs_groupmember(zfsvfs, who, cr); + break; case ACE_IDENTIFIER_GROUP: + who = zfs_gid_to_vfsgid(mnt_ns, zfs_i_user_ns(ZTOI(zp)), + who); checkit = zfs_groupmember(zfsvfs, who, cr); break; case ACE_EVERYONE: @@ -2357,6 +2454,8 @@ zfs_zaccess_aces_check(znode_t *zp, uint32_t *working_mode, default: if (entry_type == 0) { uid_t newid; + who = zfs_uid_to_vfsuid(mnt_ns, + zfs_i_user_ns(ZTOI(zp)), who); newid = zfs_fuid_map_id(zfsvfs, who, cr, ZFS_ACE_USER); @@ -2523,7 +2622,7 @@ zfs_zaccess_common(znode_t *zp, uint32_t v4_mode, uint32_t *working_mode, * Also note: DOS R/O is ignored for directories. */ if ((v4_mode & WRITE_MASK_DATA) && - S_ISDIR(ZTOI(zp)->i_mode) && + !S_ISDIR(ZTOI(zp)->i_mode) && (zp->z_pflags & ZFS_READONLY)) { return (SET_ERROR(EPERM)); } diff --git a/module/os/linux/zfs/zfs_vfsops.c b/module/os/linux/zfs/zfs_vfsops.c index 8a1456d4bef5..bc65cde9ba99 100644 --- a/module/os/linux/zfs/zfs_vfsops.c +++ b/module/os/linux/zfs/zfs_vfsops.c @@ -340,9 +340,15 @@ xattr_changed_cb(void *arg, uint64_t newval) zfsvfs_t *zfsvfs = arg; if (newval == ZFS_XATTR_OFF) { +#ifdef SB_LARGEXATTR + zfsvfs->z_sb->s_flags &= ~SB_LARGEXATTR; +#endif zfsvfs->z_flags &= ~ZSB_XATTR; } else { zfsvfs->z_flags |= ZSB_XATTR; +#ifdef SB_LARGEXATTR + zfsvfs->z_sb->s_flags |= SB_LARGEXATTR; +#endif if (newval == ZFS_XATTR_SA) zfsvfs->z_xattr_sa = B_TRUE; diff --git a/module/os/linux/zfs/zfs_vnops_os.c b/module/os/linux/zfs/zfs_vnops_os.c index a882c88a7a72..9ee58d4f9687 100644 --- a/module/os/linux/zfs/zfs_vnops_os.c +++ b/module/os/linux/zfs/zfs_vnops_os.c @@ -636,6 +636,22 @@ zfs_create(znode_t *dzp, char *name, vattr_t *vap, int excl, os = zfsvfs->z_os; zilog = zfsvfs->z_log; + /* + * For compatibility purposes with data migrated from FreeBSD + * (which will have NFSv4 ACL type), BSD file creation semantics + * are forced rather than System V. Hence on new file creation + * if NFSV4ACL we inherit GID from parent rather than take current + * process GID. This makes S_ISGID on directories a de-facto + * no-op, but we still honor setting / removing it and normal + * inheritance of the bit on new directories in case user changes + * the underlying ACL type. + */ + if ((vap->va_mask & ATTR_MODE) && + S_ISDIR(ZTOI(dzp)->i_mode) && + (zfsvfs->z_acl_type == ZFS_ACLTYPE_NFSV4)) { + vap->va_gid = KGID_TO_SGID(ZTOI(dzp)->i_gid); + } + if (zfsvfs->z_utf8 && u8_validate(name, strlen(name), NULL, U8_VALIDATE_ENTIRE, &error) < 0) { zfs_exit(zfsvfs, FTAG); @@ -1262,6 +1278,22 @@ zfs_mkdir(znode_t *dzp, char *dirname, vattr_t *vap, znode_t **zpp, return (error); zilog = zfsvfs->z_log; + /* + * For compatibility purposes with data migrated from FreeBSD + * (which will have NFSv4 ACL type), BSD file creation semantics + * are forced rather than System V. Hence on new file creation + * if NFSV4ACL we inherit GID from parent rather than take current + * process GID. This makes S_ISGID on directories a de-facto + * no-op, but we still honor setting / removing it and normal + * inheritance of the bit on new directories in case user changes + * the underlying ACL type. + */ + if ((vap->va_mask & ATTR_MODE) && + S_ISDIR(ZTOI(dzp)->i_mode) && + (zfsvfs->z_acl_type == ZFS_ACLTYPE_NFSV4)) { + vap->va_gid = KGID_TO_SGID(ZTOI(dzp)->i_gid); + } + if (dzp->z_pflags & ZFS_XATTR) { zfs_exit(zfsvfs, FTAG); return (SET_ERROR(EINVAL)); @@ -2024,10 +2056,7 @@ zfs_setattr(znode_t *zp, vattr_t *vap, int flags, cred_t *cr, zidmap_t *mnt_ns) goto out3; } - if ((mask & ATTR_SIZE) && (zp->z_pflags & ZFS_READONLY)) { - err = SET_ERROR(EPERM); - goto out3; - } + /* ZFS_READONLY will be handled in zfs_zaccess() */ /* * Verify timestamps doesn't overflow 32 bits. diff --git a/module/os/linux/zfs/zpl_xattr.c b/module/os/linux/zfs/zpl_xattr.c index 7779ad82345f..daa644b42222 100644 --- a/module/os/linux/zfs/zpl_xattr.c +++ b/module/os/linux/zfs/zpl_xattr.c @@ -275,6 +275,14 @@ zpl_xattr_list(struct dentry *dentry, char *buffer, size_t buffer_size) goto out1; rw_enter(&zp->z_xattr_lock, RW_READER); + if ((zfsvfs->z_acl_type == ZFS_ACLTYPE_NFSV4) && + ((zp->z_pflags & ZFS_ACL_TRIVIAL) == 0)) { + error = zpl_xattr_filldir(&xf, NFS41ACL_XATTR, + sizeof (NFS41ACL_XATTR) - 1); + if (error) + goto out; + } + if (zfsvfs->z_use_sa && zp->z_is_sa) { error = zpl_xattr_list_sa(&xf); if (error) @@ -1407,6 +1415,14 @@ static xattr_handler_t zpl_xattr_acl_default_handler = { #endif /* CONFIG_FS_POSIX_ACL */ +/* + * zpl_permission() gets called by linux kernel whenever it checks + * inode_permission via inode->i_op->permission. The general preference + * is to defer to the standard in-kernel permission check (generic_permission) + * wherever possible. + * + * https://www.kernel.org/doc/Documentation/filesystems/vfs.txt + */ int #if defined(HAVE_IOPS_PERMISSION_USERNS) zpl_permission(struct user_namespace *userns, struct inode *ip, int mask) @@ -1418,6 +1434,7 @@ zpl_permission(struct inode *ip, int mask) { int to_check = 0, i, ret; cred_t *cr = NULL; + zfsvfs_t *zfsvfs = NULL; /* * If NFSv4 ACLs are not being used, go back to @@ -1427,9 +1444,10 @@ zpl_permission(struct inode *ip, int mask) */ if ((ITOZSB(ip)->z_acl_type != ZFS_ACLTYPE_NFSV4) || ((ITOZ(ip)->z_pflags & ZFS_ACL_TRIVIAL && GENERIC_MASK(mask)))) { -#if (defined(HAVE_IOPS_PERMISSION_USERNS) || \ - defined(HAVE_IOPS_PERMISSION_IDMAP)) - return (generic_permission(zfs_init_idmap, ip, mask)); +#if defined(HAVE_IOPS_PERMISSION_USERNS) + return (generic_permission(userns, ip, mask)); +#elif defined(HAVE_IOPS_PERMISSION_IDMAP) + return (generic_permission(idmap, ip, mask)); #else return (generic_permission(ip, mask)); #endif @@ -1446,32 +1464,50 @@ zpl_permission(struct inode *ip, int mask) * NFSv4 ACE. Pass back to default kernel permissions check. */ if (to_check == 0) { -#if (defined(HAVE_IOPS_PERMISSION_USERNS) || \ - defined(HAVE_IOPS_PERMISSION_IDMAP)) - return (generic_permission(zfs_init_idmap, ip, mask)); +#if defined(HAVE_IOPS_PERMISSION_USERNS) + return (generic_permission(userns, ip, mask)); +#elif defined(HAVE_IOPS_PERMISSION_IDMAP) + return (generic_permission(idmap, ip, mask)); #else return (generic_permission(ip, mask)); #endif } /* - * Avoid potentially blocking in RCU walk. + * Fast path for execute checks. Do not use zfs_fastaccesschk_execute + * since it may end up granting execute access in presence of explicit + * deny entry for user / group, and it also read the ZFS ACL + * (non-cached) which we wish to avoid in RCU. */ - if (mask & MAY_NOT_BLOCK) { - return (-ECHILD); - } + if ((to_check == ACE_EXECUTE) && + (ITOZ(ip)->z_pflags & ZFS_NO_EXECS_DENIED)) + return (0); + /* + * inode permission operation may be called in rcu-walk mode + * (mask & MAY_NOT_BLOCK). If in rcu-walk mode, the filesystem must + * check the permission without blocking or storing to the inode. + * + * If a situation is encountered that rcu-walk cannot handle, + * return -ECHILD and it will be called again in ref-walk mode. + */ cr = CRED(); crhold(cr); - ret = -zfs_access(ITOZ(ip), to_check, V_ACE_MASK, cr); - if (ret != -EPERM && ret != -EACCES) { - crfree(cr); - return (ret); - } /* - * There are some situations in which capabilities - * may allow overriding the DACL. + * There are some situations in which capabilities may allow overriding + * the DACL. Skip reading ACL if requested permissions are fully + * satisfied by capabilities. + */ + + /* + * CAP_DAC_OVERRIDE may override RWX on directories, and RW on other + * files. Execute may also be overriden if at least one execute bit is + * set. This behavior is not formally documented, but is covered in + * commit messages and code comments in namei.c. + * + * CAP_DAC_READ_SEARCH may bypass file read permission checks and + * directory read and execute permission checks. */ if (S_ISDIR(ip->i_mode)) { #ifdef SB_NFSV4ACL @@ -1488,25 +1524,44 @@ zpl_permission(struct inode *ip, int mask) crfree(cr); return (0); } - crfree(cr); - return (ret); } - if (to_check == ACE_READ_DATA) { - if (capable(CAP_DAC_READ_SEARCH)) { + if (!(mask & MAY_EXEC) || (ip->i_mode & S_IXUGO)) { + if (capable(CAP_DAC_OVERRIDE)) { crfree(cr); return (0); } } - if (!(mask & MAY_EXEC) || - (zfs_fastaccesschk_execute(ITOZ(ip), cr) == 0)) { - if (capable(CAP_DAC_OVERRIDE)) { - crfree(cr); - return (0); - } + if ((to_check == ACE_READ_DATA) && + capable(CAP_DAC_READ_SEARCH)) { + crfree(cr); + return (0); + } + + if (mask & MAY_NOT_BLOCK) { + crfree(cr); + return (-ECHILD); } + zfsvfs = ZTOZSB(ITOZ(ip)); + + if ((ret = -zfs_enter_verify_zp(zfsvfs, ITOZ(ip), FTAG)) != 0) + return (ret); + +#if defined(HAVE_IOPS_PERMISSION_USERNS) + ret = -zfs_zaccess(ITOZ(ip), to_check, V_ACE_MASK, B_FALSE, cr, + userns); +#elif defined(HAVE_IOPS_PERMISSION_IDMAP) + ret = -zfs_zaccess(ITOZ(ip), to_check, V_ACE_MASK, B_FALSE, cr, + idmap); +#else + ret = -zfs_zaccess(ITOZ(ip), to_check, V_ACE_MASK, B_FALSE, cr, + zfs_init_idmap); +#endif + + zfs_exit(zfsvfs, FTAG); + crfree(cr); return (ret); } @@ -1515,7 +1570,7 @@ zpl_permission(struct inode *ip, int mask) #define ACE4_SPECIAL_OWNER 1 #define ACE4_SPECIAL_GROUP 2 #define ACE4_SPECIAL_EVERYONE 3 -#define NFS41ACL_MAX_ACES 128 +#define NFS41ACL_MAX_ACES MAX_ACL_ENTRIES #define NFS41_FLAGS (ACE_DIRECTORY_INHERIT_ACE| \ ACE_FILE_INHERIT_ACE| \ ACE_NO_PROPAGATE_INHERIT_ACE| \ @@ -1748,9 +1803,11 @@ __zpl_xattr_nfs41acl_get(struct inode *ip, const char *name, ZPL_XATTR_GET_WRAPPER(zpl_xattr_nfs41acl_get); static int -__zpl_xattr_nfs41acl_set(struct inode *ip, const char *name, +__zpl_xattr_nfs41acl_set(zidmap_t *mnt_ns, + struct inode *ip, const char *name, const void *value, size_t size, int flags) { + (void) mnt_ns; cred_t *cr = CRED(); int error, fl, naces; vsecattr_t vsecp = { .vsa_mask = (VSA_ACE | VSA_ACE_ACLFLAGS) }; @@ -1758,12 +1815,12 @@ __zpl_xattr_nfs41acl_set(struct inode *ip, const char *name, if (ITOZSB(ip)->z_acl_type != ZFS_ACLTYPE_NFSV4) return (-EOPNOTSUPP); - /* - * TODO: we may receive NULL value and size 0 - * when rmxattr() on our special xattr is called. - * A function to "strip" the ACL needs to be added - * to avoid POLA violation. - */ + if (value == NULL && size == 0) { + crhold(cr); + error = zfs_stripacl(ITOZ(ip), cr); + crfree(cr); + return (error); + } /* xdr data is 4-byte aligned */ if (((ulong_t)value % 4) != 0) { From b6de7b2ed2f446c17443e850f6b9c067e6a8a428 Mon Sep 17 00:00:00 2001 From: Umer Saleem Date: Thu, 20 Oct 2022 22:09:14 +0500 Subject: [PATCH 3/5] Add ACL wrapper libraries This commit adds common ACL libraries, libzfsacl for Linux and FreeBSD to provide helper functions to access ACLs. On Linux, libsunacl provides acl() and facl() to be consumed by vfs_zfsacl.c in Samba. libpyzfsacl.c provides python bindings for libzfsacl. Python bindings are packaged in python3-libzfsacl. A new package is added for libzfsacl and libsunacl. Authored-by: Andrew Walker Signed-off-by: Umer Saleem --- config/deb.am | 16 +- contrib/debian/control | 20 + contrib/debian/openzfs-libzfsacl1.docs | 2 + contrib/debian/openzfs-libzfsacl1.install.in | 2 + .../debian/openzfs-python3-libzfsacl.install | 2 + contrib/debian/openzfs-python3-pyzfs.install | 3 +- contrib/debian/rules.in | 1 + include/Makefile.am | 6 +- include/sunacl.h | 110 ++ include/zfsacl.h | 391 +++++ lib/Makefile.am | 3 + lib/libzfsacl/.gitignore | 3 + lib/libzfsacl/Makefile.am | 20 + lib/libzfsacl/libpyzfsacl.c | 1517 +++++++++++++++++ lib/libzfsacl/setup.py.in | 26 + lib/libzfsacl/sunacl/Makefile.am | 22 + lib/libzfsacl/sunacl/libsunacl.c | 333 ++++ lib/libzfsacl/zfsacl/Makefile.am | 32 + lib/libzfsacl/zfsacl/libzfsacl_impl_common.c | 224 +++ lib/libzfsacl/zfsacl/libzfsacl_impl_freebsd.c | 583 +++++++ lib/libzfsacl/zfsacl/libzfsacl_impl_linux.c | 974 +++++++++++ rpm/generic/zfs.spec.in | 42 +- 22 files changed, 4322 insertions(+), 10 deletions(-) create mode 100644 contrib/debian/openzfs-libzfsacl1.docs create mode 100644 contrib/debian/openzfs-libzfsacl1.install.in create mode 100644 contrib/debian/openzfs-python3-libzfsacl.install create mode 100644 include/sunacl.h create mode 100644 include/zfsacl.h create mode 100644 lib/libzfsacl/.gitignore create mode 100644 lib/libzfsacl/Makefile.am create mode 100644 lib/libzfsacl/libpyzfsacl.c create mode 100644 lib/libzfsacl/setup.py.in create mode 100644 lib/libzfsacl/sunacl/Makefile.am create mode 100644 lib/libzfsacl/sunacl/libsunacl.c create mode 100644 lib/libzfsacl/zfsacl/Makefile.am create mode 100644 lib/libzfsacl/zfsacl/libzfsacl_impl_common.c create mode 100644 lib/libzfsacl/zfsacl/libzfsacl_impl_freebsd.c create mode 100644 lib/libzfsacl/zfsacl/libzfsacl_impl_linux.c diff --git a/config/deb.am b/config/deb.am index 9e58e1905b73..d7f5c7f0e819 100644 --- a/config/deb.am +++ b/config/deb.am @@ -61,11 +61,13 @@ deb-utils: deb-local rpm-utils-initramfs pkg4=libzfs6-$${version}.$${arch}.rpm; \ pkg5=libzpool6-$${version}.$${arch}.rpm; \ pkg6=libzfs6-devel-$${version}.$${arch}.rpm; \ - pkg7=$${name}-test-$${version}.$${arch}.rpm; \ - pkg8=$${name}-dracut-$${version}.noarch.rpm; \ - pkg9=$${name}-initramfs-$${version}.$${arch}.rpm; \ - pkg10=`ls python3-pyzfs-$${version}.noarch.rpm 2>/dev/null`; \ - pkg11=`ls pam_zfs_key-$${version}.$${arch}.rpm 2>/dev/null`; \ + pkg7=libzfsacl1-$${version}.$${arch}.rpm; \ + pkg8=$${name}-test-$${version}.$${arch}.rpm; \ + pkg9=$${name}-dracut-$${version}.noarch.rpm; \ + pkg10=$${name}-initramfs-$${version}.$${arch}.rpm; \ + pkg11=`ls python3-pyzfs-$${version}.noarch.rpm 2>/dev/null`; \ + pkg12=`ls pam_zfs_key-$${version}.$${arch}.rpm 2>/dev/null`; \ + pkg13=`ls python3-libzfsacl-$${version}.$${arch}.rpm 2>/dev/null`; \ ## Arguments need to be passed to dh_shlibdeps. Alien provides no mechanism ## to do this, so we install a shim onto the path which calls the real ## dh_shlibdeps with the required arguments. @@ -81,11 +83,11 @@ deb-utils: deb-local rpm-utils-initramfs env "PATH=$${path_prepend}:$${PATH}" \ fakeroot $(ALIEN) --bump=0 --scripts --to-deb --target=$$debarch \ $$pkg1 $$pkg2 $$pkg3 $$pkg4 $$pkg5 $$pkg6 $$pkg7 \ - $$pkg8 $$pkg9 $$pkg10 $$pkg11 || exit 1; \ + $$pkg8 $$pkg9 $$pkg10 $$pkg11 $$pkg12 $$pkg13 || exit 1; \ $(RM) $${path_prepend}/dh_shlibdeps; \ rmdir $${path_prepend}; \ $(RM) $$pkg1 $$pkg2 $$pkg3 $$pkg4 $$pkg5 $$pkg6 $$pkg7 \ - $$pkg8 $$pkg9 $$pkg10 $$pkg11; + $$pkg8 $$pkg9 $$pkg10 $$pkg11 $$pkg12 $$pkg13; deb: deb-kmod deb-dkms deb-utils diff --git a/contrib/debian/control b/contrib/debian/control index 6829c0ccdf93..c8ac9043cc8a 100644 --- a/contrib/debian/control +++ b/contrib/debian/control @@ -137,6 +137,15 @@ Description: OpenZFS pool library for Linux . This zpool library provides support for managing zpools. +Package: openzfs-libzfsacl1 +Section: contrib/libs +Architecture: linux-any +Depends: ${misc:Depends}, ${shlibs:Depends} +Replaces: libzfsacl1 +Conflicts: libzfsacl1 +Description: libzfsacl is cross platform python library for accessing NFSv41 + style ACLs. + Package: openzfs-python3-pyzfs Section: contrib/python Architecture: linux-any @@ -183,6 +192,16 @@ Description: wrapper for libzfs_core C library (documentation) . This package contains the documentation. +Package: openzfs-python3-libzfsacl +Section: contrib/python +Architecture: linux-any +Depends: openzfs-libzfsacl1 + ${misc:Depends}, + ${python3:Depends} +Replaces: python3-libzfsacl +Conflicts: python3-libzfsacl +Description: Python bindings for libzfsacl1. + Package: openzfs-zfs-dkms Architecture: all Depends: dkms (>> 2.1.1.2-5), @@ -248,6 +267,7 @@ Depends: openzfs-libnvpair3 (= ${binary:Version}), openzfs-libuutil3 (= ${binary:Version}), openzfs-libzfs6 (= ${binary:Version}), openzfs-libzpool6 (= ${binary:Version}), + openzfs-python3-libzfsacl (= ${binary:Version}), python3, ${misc:Depends}, ${shlibs:Depends} diff --git a/contrib/debian/openzfs-libzfsacl1.docs b/contrib/debian/openzfs-libzfsacl1.docs new file mode 100644 index 000000000000..4302f1b2ab6a --- /dev/null +++ b/contrib/debian/openzfs-libzfsacl1.docs @@ -0,0 +1,2 @@ +COPYRIGHT +LICENSE diff --git a/contrib/debian/openzfs-libzfsacl1.install.in b/contrib/debian/openzfs-libzfsacl1.install.in new file mode 100644 index 000000000000..f5a792276c83 --- /dev/null +++ b/contrib/debian/openzfs-libzfsacl1.install.in @@ -0,0 +1,2 @@ +lib/@DEB_HOST_MULTIARCH@/libzfsacl.so.* +lib/@DEB_HOST_MULTIARCH@/libsunacl.so.* diff --git a/contrib/debian/openzfs-python3-libzfsacl.install b/contrib/debian/openzfs-python3-libzfsacl.install new file mode 100644 index 000000000000..2ae33e267c93 --- /dev/null +++ b/contrib/debian/openzfs-python3-libzfsacl.install @@ -0,0 +1,2 @@ +usr/lib/python3*/site-packages/libzfsacl-*.egg-info +usr/lib/python3*/site-packages/libzfsacl.cpython-*.so diff --git a/contrib/debian/openzfs-python3-pyzfs.install b/contrib/debian/openzfs-python3-pyzfs.install index 4606faae20a7..b26067cc8144 100644 --- a/contrib/debian/openzfs-python3-pyzfs.install +++ b/contrib/debian/openzfs-python3-pyzfs.install @@ -1 +1,2 @@ -usr/lib/python3* +usr/lib/python3*/site-packages/libzfs_core +usr/lib/python3*/site-packages/pyzfs-*.egg-info diff --git a/contrib/debian/rules.in b/contrib/debian/rules.in index 3226d604546c..af3597b42e85 100755 --- a/contrib/debian/rules.in +++ b/contrib/debian/rules.in @@ -141,6 +141,7 @@ override_dh_auto_install: override_dh_python3: dh_python3 -p openzfs-python3-pyzfs + dh_python3 -p openzfs-python3-libzfsacl override_dh_dkms: '$(CURDIR)/scripts/dkms.mkconf' -n $(NAME) -v $(DEB_VERSION_UPSTREAM) -f '$(CURDIR)/scripts/zfs-dkms.dkms' diff --git a/include/Makefile.am b/include/Makefile.am index f173064efc99..28d67381f676 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -193,8 +193,12 @@ USER_H = \ libzfs_core.h \ libzfsbootenv.h \ libzutil.h \ - thread_pool.h + thread_pool.h \ + zfsacl.h +if BUILD_LINUX +USER_H += sunacl.h +endif if CONFIG_USER libzfsdir = $(includedir)/libzfs diff --git a/include/sunacl.h b/include/sunacl.h new file mode 100644 index 000000000000..2e45c0de7939 --- /dev/null +++ b/include/sunacl.h @@ -0,0 +1,110 @@ +/* + * 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 https://opensource.org/licenses/CDDL-1.0. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright (c) 2008, 2009 Edward Tomasz NapieraƂa + * Copyright (c) 2022 Andrew Walker + * All rights reserved. + */ + +#ifndef SUNACL_H +#define SUNACL_H extern __attribute__((visibility("default"))) + +#include /* uid_t */ + +/* + * ACL_MAX_ENTRIES from + */ + +typedef struct acl_entry aclent_t; + +typedef struct ace { + uid_t a_who; /* uid or gid */ + uint32_t a_access_mask; /* read,write,... */ + uint16_t a_flags; /* see below */ + uint16_t a_type; /* allow or deny */ +} ace_t; + +/* + * The following are defined for ace_t. + */ +#define ACE_READ_DATA 0x00000001 +#define ACE_LIST_DIRECTORY 0x00000001 +#define ACE_WRITE_DATA 0x00000002 +#define ACE_ADD_FILE 0x00000002 +#define ACE_APPEND_DATA 0x00000004 +#define ACE_ADD_SUBDIRECTORY 0x00000004 +#define ACE_READ_NAMED_ATTRS 0x00000008 +#define ACE_WRITE_NAMED_ATTRS 0x00000010 +#define ACE_EXECUTE 0x00000020 +#define ACE_DELETE_CHILD 0x00000040 +#define ACE_READ_ATTRIBUTES 0x00000080 +#define ACE_WRITE_ATTRIBUTES 0x00000100 +#define ACE_DELETE 0x00010000 +#define ACE_READ_ACL 0x00020000 +#define ACE_WRITE_ACL 0x00040000 +#define ACE_WRITE_OWNER 0x00080000 +#define ACE_SYNCHRONIZE 0x00100000 + +#define ACE_FILE_INHERIT_ACE 0x0001 +#define ACE_DIRECTORY_INHERIT_ACE 0x0002 +#define ACE_NO_PROPAGATE_INHERIT_ACE 0x0004 +#define ACE_INHERIT_ONLY_ACE 0x0008 +#define ACE_SUCCESSFUL_ACCESS_ACE_FLAG 0x0010 +#define ACE_FAILED_ACCESS_ACE_FLAG 0x0020 +#define ACE_IDENTIFIER_GROUP 0x0040 +#define ACE_INHERITED_ACE 0x0080 +#define ACE_OWNER 0x1000 +#define ACE_GROUP 0x2000 +#define ACE_EVERYONE 0x4000 + +#define ACE_ACCESS_ALLOWED_ACE_TYPE 0x0000 +#define ACE_ACCESS_DENIED_ACE_TYPE 0x0001 +#define ACE_SYSTEM_AUDIT_ACE_TYPE 0x0002 +#define ACE_SYSTEM_ALARM_ACE_TYPE 0x0003 + +#define ACE_ALL_PERMS (ACE_READ_DATA|ACE_LIST_DIRECTORY|ACE_WRITE_DATA| \ + ACE_ADD_FILE|ACE_APPEND_DATA|ACE_ADD_SUBDIRECTORY|ACE_READ_NAMED_ATTRS| \ + ACE_WRITE_NAMED_ATTRS|ACE_EXECUTE|ACE_DELETE_CHILD|ACE_READ_ATTRIBUTES| \ + ACE_WRITE_ATTRIBUTES|ACE_DELETE|ACE_READ_ACL|ACE_WRITE_ACL| \ + ACE_WRITE_OWNER|ACE_SYNCHRONIZE) + +/* + * The following flags are supported by both NFSv4 ACLs and ace_t. + */ +#define ACE_NFSV4_SUP_FLAGS (ACE_FILE_INHERIT_ACE | \ + ACE_DIRECTORY_INHERIT_ACE | \ + ACE_NO_PROPAGATE_INHERIT_ACE | \ + ACE_INHERIT_ONLY_ACE | \ + ACE_IDENTIFIER_GROUP | \ + ACE_INHERITED_ACE) + +#define ACE_TYPE_FLAGS (ACE_OWNER|ACE_GROUP|ACE_EVERYONE|ACE_IDENTIFIER_GROUP) + +/* cmd's to manipulate ace acls. */ +#define ACE_GETACL 4 +#define ACE_SETACL 5 +#define ACE_GETACLCNT 6 + +int acl(const char *path, int cmd, int cnt, void *buf); +int facl(int fd, int cmd, int cnt, void *buf); + +#endif /* SUNACL_H */ diff --git a/include/zfsacl.h b/include/zfsacl.h new file mode 100644 index 000000000000..dd5dd7dc6b0e --- /dev/null +++ b/include/zfsacl.h @@ -0,0 +1,391 @@ +/* + * 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 https://opensource.org/licenses/CDDL-1.0. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright (c) 2022 Andrew Walker + * All rights reserved. + */ + +#ifndef __ZFSACL_H__ +#define __ZFSACL_H__ + +#include +#include +#include +#include +#include +#include +#include + +/* + * BRAND_ACCESS and BRAND_DEFAULT + * values chosen so can convert easily + * to FreeBSD brand POSIX with + * zfsacl_brand_t & ACL_BRAND_POSIX + */ +typedef enum { + ZFSACL_BRAND_UNKNOWN = 0, + ZFSACL_BRAND_NFSV4 = 2, + ZFSACL_BRAND_ACCESS = 3, + ZFSACL_BRAND_DEFAULT = 5, +} zfsacl_brand_t; + +typedef enum { + ZFSACL_UNDEFINED_TAG = 0, + ZFSACL_USER_OBJ = 1, // owner@ in NFSv4 + ZFSACL_GROUP_OBJ = 2, // group@ in NFSv4 + ZFSACL_EVERYONE = 3, // everyone@ -- NFSv4 only + ZFSACL_USER = 11, // named user + ZFSACL_GROUP = 12, // named group + ZFSACL_OTHER = 13, // POSIX1e only + ZFSACL_MASK = 14, // POSIX1e only +} zfsace_who_t; + +typedef enum { + ZFSACL_ENTRY_TYPE_ALLOW = 0, + ZFSACL_ENTRY_TYPE_DENY = 1, + ZFSACL_ENTRY_TYPE_AUDIT = 2, + ZFSACL_ENTRY_TYPE_ALARM = 3, +} zfsace_entry_type_t; + +struct native_acl { + void *data; + size_t datalen; + zfsacl_brand_t brand; +}; + +#ifdef __linux__ +struct zfsacl_entry { uint_t netlong[5]; }; +struct zfsacl { + size_t aclbuf_size; + zfsacl_brand_t brand; + uint_t *aclbuf; +}; +#else +#define _ACL_PRIVATE +#define zfsacl_entry acl_entry +#define zfsacl acl_t_struct +#endif + +typedef struct zfsacl_entry *zfsacl_entry_t; +typedef struct zfsacl *zfsacl_t; + +typedef unsigned int zfsace_flagset_t; +typedef unsigned int zfsace_permset_t; +typedef uid_t zfsace_id_t; +typedef unsigned int zfsacl_aclflags_t; + +#define ZFSACL_UNDEFINED_ID ((uid_t)-1) +#define ZFSACL_APPEND_ENTRY -1 +#define ZFSACL_MAX_ENTRIES 1024 + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(a) ((int)(sizeof (a)/sizeof (a[0]))) +#endif + +boolean_t zfsacl_set_fd(int _fd, zfsacl_t _acl); +boolean_t zfsacl_set_file(const char *_path_p, zfsacl_t _acl); +boolean_t zfsacl_set_link(const char *_path_p, zfsacl_t _acl); + +zfsacl_t zfsacl_get_fd(int fd, zfsacl_brand_t _brand); +zfsacl_t zfsacl_get_file(const char *_path_p, zfsacl_brand_t _brand); +zfsacl_t zfsacl_get_link(const char *_path_p, zfsacl_brand_t _brand); + +boolean_t zfsacl_is_trivial(zfsacl_t _acl, boolean_t *trivialp); + +/* + * @brief initialize a new ZFS ACL (for setting on file) + * allocates memory that must be freed + * + * @param[in] _acecnt count of ACEs for new ACL + * @param[in] _brand brand of ACL to allocate + * @return new ACL on succcess, NULL on failure + */ +zfsacl_t zfsacl_init(int _acecnt, zfsacl_brand_t _brand); + +/* + * @brief free an ACL + * + * @param[in] *_acl free an ACL + * @return always succeeds + */ +void zfsacl_free(zfsacl_t *_acl); + +/* + * @brief get branding for specified ACL + * + * @param[in] _acl the ACL from which to get branding info + * @param[out] _brandp the brand (ACCESS, DEFAULT, NFSV4) + * @return B_TRUE on success, B_FALSE on failure + */ +boolean_t zfsacl_get_brand(zfsacl_t _acl, zfsacl_brand_t *_brandp); + +/* + * API to get / set ACL-wide flags + * these are NFSv41-only + */ + +/* + * @brief get ACL-wide flags + * + * @param[in] _acl the ZFS ACL + * @param[out] _paclflags ACL-wide flags + * @return B_TRUE on success, B_FALSE on failure + */ +boolean_t zfsacl_get_aclflags(zfsacl_t _acl, zfsacl_aclflags_t *_paclflags); + +/* + * @brief set ACL-wide flags + * + * @param[in] _acl ZFS ACL to modify + * @param[in] _aclflags flags to set on ACL + * @return B_TRUE on success, B_FALSE on failure + */ +boolean_t zfsacl_set_aclflags(zfsacl_t _acl, zfsacl_aclflags_t _aclflags); + +/* + * @brief get number of ACL entries in ACL + * + * @param[in] _acl the ZFS ACL + * @param[out] _acecnt number of ACEs in ACL. + * @return B_TRUE on success, B_FALSE on failure + */ +boolean_t zfsacl_get_acecnt(zfsacl_t _acl, uint_t *_acecnt); + +/* + * API to get, create, modify, and delete ACL entries + */ + +/* + * @brief create ACL entry at specified index + * special value ZFSACL_APPEND_ENTRY will create new entry + * at end of list. + * + * @param[in] _acl the ZFS ACL to modify + * @param[in] _idx index of where to create new ACL entry + * @param[out] _pentry new ACL entry created + * @return B_TRUE on success, B_FALSE on failure + */ +boolean_t zfsacl_create_aclentry(zfsacl_t _acl, int _idx, + zfsacl_entry_t *_pentry); + +/* + * @brief get ACL entry at specified index + * + * @param[in] _acl ZFS ACL from which to get entry + * @param[in] _idx index of ACL entry to retrieve + * @param[out] _pentry ACL entry retrieved + * @return B_TRUE on success, B_FALSE on failure + */ +boolean_t zfsacl_get_aclentry(zfsacl_t _acl, int _idx, + zfsacl_entry_t *_pentry); + +/* + * @brief remove ACL entry by index + * + * @param[in] _acl ZFS ACL from which to remove entry + * @param[in] _idx index of ACL entry to remove + * @return B_TRUE on success, B_FALSE on failure + */ +boolean_t zfsacl_delete_aclentry(zfsacl_t _acl, int _idx); + + +/* + * @brief convert an ACL to text. Returns malloced string. + * + * @param[in] _acl ZFS ACL + * @return pointer to text form the of specified ACLe + */ +char *zfsacl_to_text(zfsacl_t _acl); + +boolean_t zfsacl_to_native(zfsacl_t _acl, struct native_acl *pnative); + +/* + * ACL entry specific functions + */ +boolean_t zfsace_get_permset(zfsacl_entry_t _entry, + zfsace_permset_t *_pperm); +boolean_t zfsace_get_flagset(zfsacl_entry_t _entry, + zfsace_flagset_t *_pflags); +boolean_t zfsace_get_who(zfsacl_entry_t _entry, zfsace_who_t *pwho, + zfsace_id_t *_paeid); +boolean_t zfsace_get_entry_type(zfsacl_entry_t _entry, + zfsace_entry_type_t *_tp); + +boolean_t zfsace_set_permset(zfsacl_entry_t _entry, zfsace_permset_t _perm); +boolean_t zfsace_set_flagset(zfsacl_entry_t _entry, zfsace_flagset_t _flags); +boolean_t zfsace_set_who(zfsacl_entry_t _entry, zfsace_who_t _who, + zfsace_id_t _aeid); +boolean_t zfsace_set_entry_type(zfsacl_entry_t _entry, zfsace_entry_type_t _tp); + +zfsacl_t zfsacl_calculate_inherited_acl(zfsacl_t p, zfsacl_t t, boolean_t dir); + + +/* + * NFSv4 ACL-wide flags + * used in zfsacl_get_aclflags() and zfsacl_set_aclflags() + */ + +/* + * ACL flags + */ +#define ZFSACL_AUTO_INHERIT 0x0001 +#define ZFSACL_PROTECTED 0x0002 +#define ZFSACL_DEFAULTED 0x0004 +#define ZFSACL_FLAGS_ALL \ + (ZFSACL_AUTO_INHERIT|ZFSACL_PROTECTED|ZFSACL_DEFAULTED) + +#define ZFSACL_FLAGS_INVALID(flags) (flags & ~ZFSACL_FLAGS_ALL) +/* + * ZFS pflags exposed via ACL call as ACL flags + * valid on get, but not set + */ +#define ZFSACL_IS_TRIVIAL 0x10000 +#define ZFSACL_IS_DIR 0x20000 + +#define ZFSACE_TYPE_INVALID(ae_type) (ae_type > ZFSACL_ENTRY_TYPE_DENY) + +/* + * NFSv4 ACL inheritance flags + * These are not valid if ACL is branded POSIX ACCESS or DEFAULT + */ +#define ZFSACE_FILE_INHERIT 0x00000001 +#define ZFSACE_DIRECTORY_INHERIT 0x00000002 +#define ZFSACE_NO_PROPAGATE_INHERIT 0x00000004 +#define ZFSACE_INHERIT_ONLY 0x00000008 +#define ZFSACE_SUCCESSFUL_ACCESS_ACE_FLAG 0x00000010 +#define ZFSACE_FAILED_ACCESS_ACE_FLAG 0x00000020 +#define ZFSACE_IDENTIFIER_GROUP 0x00000040 +#define ZFSACE_INHERITED_ACE 0x00000080 + +#define ZFSACE_IS_GROUP(flags) (flags & ZFSACE_IDENTIFIER_GROUP) + +#define ZFSACE_FLAG_INVALID(flags) ((flags & 0xFFFFFF30) || ( \ + (flags & ZFSACE_INHERIT_ONLY) && \ + (flags & !(ZFSACE_FILE_INHERIT | ZFSACE_DIRECTORY_INHERIT)))) + +/* + * NFSv4 ACL permissions + */ +#define ZFSACE_READ_DATA 0x00000001 +#define ZFSACE_LIST_DIRECTORY 0x00000001 +#define ZFSACE_WRITE_DATA 0x00000002 +#define ZFSACE_ADD_FILE 0x00000002 +#define ZFSACE_APPEND_DATA 0x00000004 +#define ZFSACE_ADD_SUBDIRECTORY 0x00000004 +#define ZFSACE_READ_NAMED_ATTRS 0x00000008 +#define ZFSACE_WRITE_NAMED_ATTRS 0x00000010 +#define ZFSACE_EXECUTE 0x00000020 +#define ZFSACE_DELETE_CHILD 0x00000040 +#define ZFSACE_READ_ATTRIBUTES 0x00000080 +#define ZFSACE_WRITE_ATTRIBUTES 0x00000100 +#define ZFSACE_DELETE 0x00010000 +#define ZFSACE_READ_ACL 0x00020000 +#define ZFSACE_WRITE_ACL 0x00040000 +#define ZFSACE_WRITE_OWNER 0x00080000 +#define ZFSACE_SYNCHRONIZE 0x00100000 + +#define ZFSACE_FULL_SET (ZFSACE_READ_DATA | ZFSACE_WRITE_DATA | \ + ZFSACE_APPEND_DATA | ZFSACE_READ_NAMED_ATTRS | ZFSACE_WRITE_NAMED_ATTRS | \ + ZFSACE_EXECUTE | ZFSACE_DELETE_CHILD | ZFSACE_READ_ATTRIBUTES | \ + ZFSACE_WRITE_ATTRIBUTES | ZFSACE_DELETE | ZFSACE_READ_ACL | \ + ZFSACE_WRITE_ACL | ZFSACE_WRITE_OWNER | ZFSACE_SYNCHRONIZE) + +#define ZFSACE_MODIFY_SET (ZFSACE_FULL_SET & \ + ~(ZFSACE_WRITE_ACL | ZFSACE_WRITE_OWNER)) + +#define ZFSACE_READ_SET (ZFSACE_READ_DATA | ZFSACE_READ_NAMED_ATTRS | \ + ZFSACE_READ_ATTRIBUTES | ZFSACE_READ_ACL) + +#define ZFSACE_WRITE_SET (ZFSACE_WRITE_DATA | ZFSACE_APPEND_DATA | \ + ZFSACE_WRITE_NAMED_ATTRS | ZFSACE_WRITE_ATTRIBUTES) + +#define ZFSACE_TRAVERSE_SET (ZFSACE_EXECUTE | ZFSACE_READ_NAMED_ATTRS | \ + ZFSACE_READ_ATTRIBUTES | ZFSACE_READ_ACL) + +#define ZFSACE_ACCESS_MASK_INVALID(mask) (mask & ~ZFSACE_FULL_SET) + +#define SPECIAL_WHO_INVALID(who) ( \ + (who != ZFSACL_USER_OBJ) && (who != ZFSACL_USER) && \ + (who != ZFSACL_GROUP_OBJ) && (who != ZFSACL_GROUP) && \ + (who != ZFSACL_EVERYONE)) + +static const struct { + zfsacl_aclflags_t flag; + const char *name; +} aclflag2name[] = { + { ZFSACL_AUTO_INHERIT, "AUTO_INHERIT" }, + { ZFSACL_PROTECTED, "PROTECTED" }, + { ZFSACL_DEFAULTED, "DEFAULTED" }, + { ZFSACL_IS_TRIVIAL, "ACL_IS_TRIVIAL" }, + { ZFSACL_IS_DIR, "IS_DIRECTORY" }, +}; + +static const struct { + zfsace_permset_t perm; + const char *name; + char letter; +} aceperm2name[] = { + { ZFSACE_READ_DATA, "READ_DATA", 'r' }, + { ZFSACE_LIST_DIRECTORY, "LIST_DIRECTORY", '\0' }, + { ZFSACE_WRITE_DATA, "WRITE_DATA", 'w' }, + { ZFSACE_ADD_FILE, "ADD_FILE", '\0' }, + { ZFSACE_APPEND_DATA, "APPEND_DATA", 'p' }, + { ZFSACE_DELETE, "DELETE", 'd' }, + { ZFSACE_DELETE_CHILD, "DELETE_CHILD", 'D' }, + { ZFSACE_ADD_SUBDIRECTORY, "ADD_SUBDIRECTORY", '\0' }, + { ZFSACE_READ_ATTRIBUTES, "READ_ATTRIBUTES", 'a' }, + { ZFSACE_WRITE_ATTRIBUTES, "WRITE_ATTRIBUTES", 'A' }, + { ZFSACE_READ_NAMED_ATTRS, "READ_NAMED_ATTRS", 'R' }, + { ZFSACE_WRITE_NAMED_ATTRS, "WRITE_NAMED_ATTRS", 'W' }, + { ZFSACE_READ_ACL, "READ_ACL", 'c' }, + { ZFSACE_WRITE_ACL, "WRITE_ACL", 'C' }, + { ZFSACE_WRITE_OWNER, "WRITE_OWNER", 'o' }, + { ZFSACE_SYNCHRONIZE, "SYNCHRONIZE", 's' }, +}; + +static const struct { + zfsace_flagset_t flag; + const char *name; + char letter; +} aceflag2name[] = { + { ZFSACE_FILE_INHERIT, "FILE_INHERIT", 'f' }, + { ZFSACE_DIRECTORY_INHERIT, "DIRECTORY_INHERIT", 'd' }, + { ZFSACE_INHERIT_ONLY, "INHERIT_ONLY", 'i' }, + { ZFSACE_NO_PROPAGATE_INHERIT, "NO_PROPAGATE_INHERIT", 'n' }, + { ZFSACE_INHERITED_ACE, "INHERITED", 'I' }, +}; + +static const struct { + zfsace_who_t who; + const char *name; +} acewho2name[] = { + { ZFSACL_UNDEFINED_TAG, "UNDEFINED" }, + { ZFSACL_USER_OBJ, "USER_OBJ" }, + { ZFSACL_GROUP_OBJ, "GROUP_OBJ" }, + { ZFSACL_EVERYONE, "EVERYONE" }, + { ZFSACL_USER, "USER" }, + { ZFSACL_GROUP, "GROUP" }, + { ZFSACL_OTHER, "OTHER" }, + { ZFSACL_MASK, "MASK" }, +}; + +#endif /* __ZFSACL_H__ */ diff --git a/lib/Makefile.am b/lib/Makefile.am index 050a6cac0a37..51c518235b3f 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -65,12 +65,15 @@ include $(srcdir)/%D%/libuutil/Makefile.am include $(srcdir)/%D%/libzdb/Makefile.am include $(srcdir)/%D%/libzfs_core/Makefile.am include $(srcdir)/%D%/libzfs/Makefile.am +include $(srcdir)/%D%/libzfsacl/zfsacl/Makefile.am +include $(srcdir)/%D%/libzfsacl/Makefile.am include $(srcdir)/%D%/libzfsbootenv/Makefile.am include $(srcdir)/%D%/libzpool/Makefile.am include $(srcdir)/%D%/libzstd/Makefile.am include $(srcdir)/%D%/libzutil/Makefile.am if BUILD_LINUX include $(srcdir)/%D%/libefi/Makefile.am +include $(srcdir)/%D%/libzfsacl/sunacl/Makefile.am endif diff --git a/lib/libzfsacl/.gitignore b/lib/libzfsacl/.gitignore new file mode 100644 index 000000000000..15c7e36e74c6 --- /dev/null +++ b/lib/libzfsacl/.gitignore @@ -0,0 +1,3 @@ +build +libzfsacl.egg-info +setup.py diff --git a/lib/libzfsacl/Makefile.am b/lib/libzfsacl/Makefile.am new file mode 100644 index 000000000000..2d63efc27a52 --- /dev/null +++ b/lib/libzfsacl/Makefile.am @@ -0,0 +1,20 @@ +dist_noinst_DATA += \ + %D%/libpyzfsacl.c + +SUBSTFILES += %D%/setup.py + +ALL_LOCAL += libzfsacl-all-local +libzfsacl-all-local: %D%/setup.py + cd %D% && $(PYTHON) setup.py egg_info -e . build + +INSTALL_DATA_HOOKS += libzfsacl-install-data-hook +libzfsacl-install-data-hook: + cd %D% && $(PYTHON) setup.py egg_info -e . install \ + --prefix $(prefix) \ + --root $(DESTDIR)/ \ + --install-lib $(pythonsitedir) \ + --verbose + +CLEAN_LOCAL += libzfsacl-clean-local +libzfsacl-clean-local: + -$(RM) -r %D%/build/ diff --git a/lib/libzfsacl/libpyzfsacl.c b/lib/libzfsacl/libpyzfsacl.c new file mode 100644 index 000000000000..618eaa105f2f --- /dev/null +++ b/lib/libzfsacl/libpyzfsacl.c @@ -0,0 +1,1517 @@ +/* + * 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 https://opensource.org/licenses/CDDL-1.0. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright (c) 2022 Andrew Walker + * All rights reserved. + */ + +#include +#include +#include + +#define Py_TPFLAGS_HAVE_ITER 0 + +typedef struct { + PyObject_HEAD + boolean_t verbose; + zfsacl_t theacl; +} py_acl; + +typedef struct { + PyObject_HEAD + py_acl *parent_acl; + int idx; + uint_t initial_cnt; + zfsacl_entry_t theace; +} py_acl_entry; + +typedef struct { + PyObject_HEAD + py_acl *acl; + int current_idx; +} py_acl_iterator; + +static PyObject *py_acl_inherit(PyObject *, PyObject *, PyObject *); + +static void +set_exc_from_errno(const char *func) +{ + PyErr_Format( + PyExc_RuntimeError, + "%s failed: %s", func, strerror(errno)); +} + +static PyObject * +py_acl_iter_next(py_acl_iterator *self) +{ + PyObject *out = NULL; + + out = PyObject_CallMethod( + (PyObject *)self->acl, "get_entry", "i", self->current_idx); + + if (out == NULL) { + if (PyErr_Occurred() == NULL) { + return (NULL); + } + if (PyErr_ExceptionMatches(PyExc_IndexError)) { + /* iteration done */ + PyErr_Clear(); + PyErr_SetNone(PyExc_StopIteration); + return (NULL); + } + /* Some other error occurred */ + return (NULL); + } + + self->current_idx++; + return (out); +} + +static void +py_acl_iter_dealloc(py_acl_iterator *self) +{ + Py_CLEAR(self->acl); + PyObject_Del(self); +} + +PyTypeObject PyACLIterator = { + .tp_name = "ACL Iterator", + .tp_basicsize = sizeof (py_acl_iterator), + .tp_iternext = (iternextfunc)py_acl_iter_next, + .tp_dealloc = (destructor)py_acl_iter_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_iter = PyObject_SelfIter, +}; + +static inline PyObject * +aclflag_to_pylist(zfsacl_aclflags_t flags) +{ + int i, err; + PyObject *out = NULL; + + out = Py_BuildValue("[]"); + if (out == NULL) { + return (NULL); + } + + for (i = 0; i < ARRAY_SIZE(aclflag2name); i++) { + PyObject *val = NULL; + + if ((flags & aclflag2name[i].flag) == 0) { + continue; + } + + val = Py_BuildValue("s", aclflag2name[i].name); + if (val == NULL) { + Py_DECREF(out); + return (NULL); + } + + err = PyList_Append(out, val); + Py_XDECREF(val); + if (err == -1) { + Py_XDECREF(out); + return (NULL); + } + } + + return (out); +} + +static PyObject * +permset_to_pylist(zfsace_permset_t perms) +{ + int i, err; + PyObject *out = NULL; + + out = Py_BuildValue("[]"); + if (out == NULL) { + return (NULL); + } + + for (i = 0; i < ARRAY_SIZE(aceperm2name); i++) { + PyObject *val = NULL; + + if ((perms & aceperm2name[i].perm) == 0) { + continue; + } + + val = Py_BuildValue("s", aceperm2name[i].name); + if (val == NULL) { + Py_DECREF(out); + return (NULL); + } + + err = PyList_Append(out, val); + Py_XDECREF(val); + if (err == -1) { + Py_XDECREF(out); + return (NULL); + } + } + + return (out); +} + +static PyObject * +flagset_to_pylist(zfsace_flagset_t flags) +{ + int i, err; + PyObject *out = NULL; + out = Py_BuildValue("[]"); + if (out == NULL) { + return (NULL); + } + + for (i = 0; i < ARRAY_SIZE(aceflag2name); i++) { + PyObject *val = NULL; + + if ((flags & aceflag2name[i].flag) == 0) { + continue; + } + + val = Py_BuildValue("s", aceflag2name[i].name); + if (val == NULL) { + Py_DECREF(out); + return (NULL); + } + + err = PyList_Append(out, val); + Py_XDECREF(val); + if (err == -1) { + Py_XDECREF(out); + return (NULL); + } + } + + return (out); +} + +static PyObject * +whotype_to_pystring(zfsace_who_t whotype) +{ + int i; + PyObject *out = NULL; + + for (i = 0; i < ARRAY_SIZE(acewho2name); i++) { + if (whotype != acewho2name[i].who) { + continue; + } + + out = Py_BuildValue("s", acewho2name[i].name); + if (out == NULL) { + return (NULL); + } + return (out); + } + PyErr_Format(PyExc_ValueError, "%d is an invalid whotype", whotype); + + return (NULL); +} + +static PyObject * +py_ace_new(PyTypeObject *obj, PyObject *args_unused, + PyObject *kwargs_unused) +{ + py_acl_entry *self = NULL; + + self = (py_acl_entry *)obj->tp_alloc(obj, 0); + if (self == NULL) { + return (NULL); + } + self->theace = NULL; + self->parent_acl = NULL; + return ((PyObject *)self); +} + +static int +py_ace_init(PyObject *obj, PyObject *args, PyObject *kwargs) +{ + return (0); +} + +static void +py_ace_dealloc(py_acl_entry *self) +{ + if (self->parent_acl != NULL) { + Py_CLEAR(self->parent_acl); + } + + /* + * memory for ACL entry will be freed when + * ACL is deallocated. + */ + self->theace = NULL; + Py_TYPE(self)->tp_free((PyObject *)self); +} + +static PyObject * +permset_to_basic(zfsace_permset_t perms) +{ + PyObject *out = NULL; + + if (perms == ZFSACE_FULL_SET) { + out = Py_BuildValue("s", "FULL_CONTROL"); + return (out); + } else if (perms == ZFSACE_MODIFY_SET) { + out = Py_BuildValue("s", "MODIFY"); + return (out); + } else if (perms == (ZFSACE_READ_SET | ZFSACE_EXECUTE)) { + out = Py_BuildValue("s", "READ"); + return (out); + } else if (perms == ZFSACE_TRAVERSE_SET) { + out = Py_BuildValue("s", "TRAVERSE"); + return (out); + } + + Py_RETURN_NONE; +} + +static PyObject * +ace_get_permset(PyObject *obj, void *closure) +{ + py_acl_entry *self = (py_acl_entry *)obj; + py_acl *acl = self->parent_acl; + + boolean_t ok; + zfsace_permset_t perms; + PyObject *out = NULL; + + ok = zfsace_get_permset(self->theace, &perms); + if (!ok) { + set_exc_from_errno("zfsace_get_permset()"); + return (NULL); + } + + if (acl && acl->verbose) { + PyObject *permlist = NULL; + PyObject *basic = NULL; + + permlist = permset_to_pylist(perms); + if (permlist == NULL) { + return (NULL); + } + + basic = permset_to_basic(perms); + if (basic == NULL) { + Py_XDECREF(permlist); + return (NULL); + } + + out = Py_BuildValue("{s:I,s:O,s:O}", "raw", perms, + "parsed", permlist, "basic", basic); + + Py_XDECREF(permlist); + Py_XDECREF(basic); + } else { + out = Py_BuildValue("I", perms); + } + + return (out); +} + +static boolean_t +parse_permset(py_acl *acl, PyObject *to_parse, + zfsace_permset_t *permset) +{ + unsigned long py_permset; + + if (!PyLong_Check(to_parse)) + return (B_FALSE); + + py_permset = PyLong_AsUnsignedLong(to_parse); + + if (py_permset == (unsigned long) -1) + return (B_FALSE); + + if (ZFSACE_ACCESS_MASK_INVALID(py_permset)) { + PyErr_SetString(PyExc_ValueError, "invalid flagset."); + return (B_FALSE); + } + + *permset = (zfsace_permset_t)py_permset; + return (B_TRUE); +} + +static int +ace_set_permset(PyObject *obj, PyObject *value, void *closure) +{ + py_acl_entry *self = (py_acl_entry *)obj; + py_acl *acl = self->parent_acl; + boolean_t ok; + zfsace_permset_t permset; + + ok = parse_permset(acl, value, &permset); + if (!ok) { + return (-1); + } + + ok = zfsace_set_permset(self->theace, permset); + if (!ok) { + set_exc_from_errno("zfsace_set_permset()"); + return (-1); + } + return (0); +} + +static PyObject * +flagset_to_basic(zfsace_flagset_t flags) +{ + PyObject *out = NULL; + + /* inherited does not affect consideration of basic */ + flags &= ~ZFSACE_INHERITED_ACE; + + if (flags == (ZFSACE_DIRECTORY_INHERIT | ZFSACE_FILE_INHERIT)) { + out = Py_BuildValue("s", "INHERIT"); + return (out); + } else if (flags == 0) { + out = Py_BuildValue("s", "NO_INHERIT"); + return (out); + } + + Py_RETURN_NONE; +} + +static PyObject * +ace_get_flagset(PyObject *obj, void *closure) +{ + py_acl_entry *self = (py_acl_entry *)obj; + py_acl *acl = self->parent_acl; + boolean_t ok; + zfsace_flagset_t flags; + PyObject *out = NULL; + + ok = zfsace_get_flagset(self->theace, &flags); + if (!ok) { + set_exc_from_errno("zfsace_get_flagset()"); + return (NULL); + } + + if (acl && acl->verbose) { + PyObject *flaglist = NULL; + PyObject *basic = NULL; + flaglist = flagset_to_pylist(flags); + if (flaglist == NULL) { + return (NULL); + } + + basic = flagset_to_basic(flags); + if (basic == NULL) { + Py_XDECREF(flaglist); + return (NULL); + } + + out = Py_BuildValue("{s:I,s:O,s:O}", "raw", flags, "parsed", + flaglist, "basic", basic); + + Py_XDECREF(flaglist); + Py_XDECREF(basic); + } else { + out = Py_BuildValue("I", flags); + } + + return (out); +} + +static boolean_t +parse_flagset(py_acl *acl, PyObject *to_parse, + zfsace_flagset_t *flagset) +{ + unsigned long py_flagset; + + if (!PyLong_Check(to_parse)) + return (B_FALSE); + + py_flagset = PyLong_AsUnsignedLong(to_parse); + + if (py_flagset == (unsigned long) -1) + return (B_FALSE); + + if (ZFSACE_FLAG_INVALID(py_flagset)) { + PyErr_SetString(PyExc_ValueError, "invalid flagset."); + return (B_FALSE); + } + + *flagset = (zfsace_flagset_t)py_flagset; + return (B_TRUE); +} + +static int +ace_set_flagset(PyObject *obj, PyObject *value, void *closure) +{ + py_acl_entry *self = (py_acl_entry *)obj; + py_acl *acl = self->parent_acl; + boolean_t ok; + zfsace_flagset_t flagset; + + ok = parse_flagset(acl, value, &flagset); + if (!ok) { + return (-1); + } + + ok = zfsace_set_flagset(self->theace, flagset); + if (!ok) { + set_exc_from_errno("zfsace_set_flagset()"); + return (-1); + } + return (0); +} + +static PyObject * +verbose_who(zfsace_who_t whotype, zfsace_id_t whoid) +{ + PyObject *pywhotype = NULL; + PyObject *pywhoid = NULL; + PyObject *verbose_whotype = NULL; + PyObject *out = NULL; + + pywhotype = whotype_to_pystring(whotype); + if (pywhotype == NULL) { + return (NULL); + } + + verbose_whotype = Py_BuildValue("{s:I,s:O}", "raw", whotype, + "parsed", pywhotype); + + Py_XDECREF(pywhotype); + + /* + * In future it may make sense to add getpwuid_r / getgrgid_r call here + */ + pywhoid = Py_BuildValue("{s:I,s:I}", "raw", whoid, "parsed", whoid); + + if (pywhoid == NULL) { + Py_XDECREF(verbose_whotype); + return (NULL); + } + + out = Py_BuildValue("{s:O,s:O}", "who_type", verbose_whotype, + "who_id", pywhoid); + + Py_XDECREF(verbose_whotype); + Py_XDECREF(pywhoid); + return (out); +} + +static PyObject * +ace_get_who(PyObject *obj, void *closure) +{ + py_acl_entry *self = (py_acl_entry *)obj; + py_acl *acl = self->parent_acl; + boolean_t ok; + zfsace_who_t whotype; + zfsace_id_t whoid; + PyObject *out = NULL; + + ok = zfsace_get_who(self->theace, &whotype, &whoid); + if (!ok) { + set_exc_from_errno("zfsace_get_who()"); + return (NULL); + } + + if (acl && acl->verbose) { + out = verbose_who(whotype, whoid); + } else { + out = Py_BuildValue("II", whotype, whoid); + } + return (out); +} + +static boolean_t +parse_who(py_acl *acl, PyObject *to_parse, zfsace_who_t *whotype, + zfsace_id_t *whoid) +{ + int pywhotype, pywhoid; + + if (!PyArg_ParseTuple(to_parse, "ii", &pywhotype, &pywhoid)) + return (B_FALSE); + + if (SPECIAL_WHO_INVALID(pywhotype)) { + PyErr_SetString(PyExc_ValueError, "invalid whotype."); + return (B_FALSE); + } + + if ((pywhoid < 0) && (pywhoid != -1)) { + PyErr_SetString(PyExc_ValueError, "invalid id"); + return (B_FALSE); + } + + if ((pywhoid == -1) && + ((pywhotype == ZFSACL_USER) || (pywhotype == ZFSACL_USER))) { + PyErr_SetString(PyExc_ValueError, + "-1 is invalid ID for named entries."); + return (B_FALSE); + } + + if (pywhoid > INT32_MAX) { + PyErr_SetString(PyExc_ValueError, + "ID for named entry is too large."); + return (B_FALSE); + } + + *whotype = (zfsace_who_t)pywhotype; + *whoid = (zfsace_id_t)pywhoid; + + return (B_TRUE); +} + +static int +ace_set_who(PyObject *obj, PyObject *value, void *closure) +{ + py_acl_entry *self = (py_acl_entry *)obj; + py_acl *acl = self->parent_acl; + zfsace_who_t whotype; + zfsace_id_t whoid; + boolean_t ok; + + ok = parse_who(acl, value, &whotype, &whoid); + if (!ok) { + return (-1); + } + + ok = zfsace_set_who(self->theace, whotype, whoid); + if (!ok) { + set_exc_from_errno("zfsace_set_who()"); + return (-1); + } + return (0); +} + +static PyObject * +ace_get_entry_type(PyObject *obj, void *closure) +{ + py_acl_entry *self = (py_acl_entry *)obj; + py_acl *acl = self->parent_acl; + boolean_t ok; + zfsace_entry_type_t entry_type; + PyObject *out = NULL; + + ok = zfsace_get_entry_type(self->theace, &entry_type); + if (!ok) { + set_exc_from_errno("zfsace_get_entry_type()"); + return (NULL); + } + + if (acl && acl->verbose) { + const char *entry_str = NULL; + + switch (entry_type) { + case ZFSACL_ENTRY_TYPE_ALLOW: + entry_str = "ALLOW"; + break; + case ZFSACL_ENTRY_TYPE_DENY: + entry_str = "DENY"; + break; + default: + PyErr_Format(PyExc_ValueError, + "%d is an invalid entry type", entry_type); + return (NULL); + } + out = Py_BuildValue("{s:I,s:s}", "raw", entry_type, "parsed", + entry_str); + } else { + out = Py_BuildValue("I", entry_type); + } + return (out); +} + +static boolean_t +parse_entry_type(py_acl *acl, PyObject *to_parse, + zfsace_entry_type_t *entry_type) +{ + unsigned long py_entry_type; + + + if (!PyLong_Check(to_parse)) + return (B_FALSE); + py_entry_type = PyLong_AsUnsignedLong(to_parse); + + if (py_entry_type == (unsigned long) -1) + return (B_FALSE); + + if (ZFSACE_TYPE_INVALID(py_entry_type)) { + PyErr_SetString(PyExc_ValueError, "invalid ACL entry type."); + return (B_FALSE); + } + + *entry_type = (zfsace_entry_type_t)py_entry_type; + return (B_TRUE); +} + +static int ace_set_entry_type(PyObject *obj, PyObject *value, + void *closure) +{ + py_acl_entry *self = (py_acl_entry *)obj; + py_acl *acl = self->parent_acl; + boolean_t ok; + zfsace_entry_type_t entry_type; + + ok = parse_entry_type(acl, value, &entry_type); + if (!ok) { + return (-1); + } + + ok = zfsace_set_entry_type(self->theace, entry_type); + if (!ok) { + set_exc_from_errno("zfsace_set_entry_type()"); + return (-1); + } + + return (0); +} + +static PyObject * +ace_get_idx(PyObject *obj, void *closure) +{ + py_acl_entry *self = (py_acl_entry *)obj; + return (Py_BuildValue("i", self->idx)); +} + +static PyMethodDef ace_object_methods[] = { + { NULL, NULL, 0, NULL } +}; + +/* BEGIN CSTYLED */ +PyDoc_STRVAR(py_ace_idx__doc__, + "Position of Access control entry in the ACL.\n"); + +PyDoc_STRVAR(py_ace_permset__doc__, +"int : access mask for the access control list entry.\n" +"This should be bitwise or of following values as defined\n" +"in RFC 3530 Section 5.11.2.\n\n" +"Values\n" +"------\n" +"NFSv4 and POSIX1E common permissions:\n" +"zfsacl.PERM_READ_DATA - Permission to read data of the file\n" +"zfsacl.PERM_WRITE_DATA - Permission to modify file's data\n" +"zfsacl.PERM_EXECUTE - Permission to execute a file\n" +"NFSv4 brand specific permissions:\n" +"zfsacl.PERM_LIST_DIRECTORY - Permission to list contents of " +"a directory\n" +"zfsacl.PERM_ADD_FILE - Permission to add a new file to a directory\n" +"zfsacl.PERM_APPEND_DATA - Permission to append data to a file\n" +"zfsacl.PERM_ADD_SUBDIRECTORY - Permission to create a subdirectory " +"to a directory\n" +"zfsacl.PERM_READ_NAMED_ATTRS - Permission to read the named " +"attributes of a file\n" +"zfsacl.PERM_WRITE_NAMED_ATTRS - Permission to write the named " +"attributes of a file\n" +"zfsacl.PERM_DELETE_CHILD - Permission to delete a file or directory " +"within a directorey\n" +"zfsacl.PERM_READ_ATTRIBUTES - Permission to stat() a file\n" +"zfsacl.PERM_WRITE_ATTRIBUTES - Permission to change basic attributes\n" +"zfsacl.PERM_DELETE - Permission to delete the file\n" +"zfsacl.PERM_WRITE_ACL - Permission to write the ACL\n" +"zfsacl.PERM_WRITE_OWNER - Permission to change the owner\n" +"zfsacl.PERM_SYNCHRONIZE - Not Implemented\n\n" +"Warning\n" +"-------\n" +"The exact behavior of these permissions bits may vary depending\n" +"on operating system implementation. Please review relevant OS\n" +"documention and validate the behavior before deploying an access\n" +"control scheme in a production environment.\n" +); + +PyDoc_STRVAR(py_ace_flagset__doc__, +"int : inheritance flags for the access control list entry.\n" +"This should be bitwise or of the following values as defined\n" +"in RFC 5661 Section 6.2.1.4.\n\n" +"Values\n" +"------\n" +"zfsacl.FLAG_FILE_INHERIT - Any non-directory file in any subdirectory\n" +"will get this ACE inherited\n" +"zfsacl.FLAG_DIRECTORY_INHERIT - This ACE will be added to any new " +"subdirectory created in this directory\n" +"zfsacl.FLAG_NO_PROPAGATE_INHERIT - Inheritance of this ACE should stop\n" +"at newly created child directories\n" +"zfsacl.FLAG_INHERIT_ONLY - ACE is not enforced on this directory, but\n" +"will be enforced (cleared) on newly created files and directories\n" +"zfsacl.FLAG_INHERITED - This ace was inherited from a parent directory\n\n" +"Note: flags are not valid for POSIX1E ACLs. The only flag valid for\n" +"files is zfsacl.FLAG_INHERITED, presence of other flags in any ACL entries\n" +"in an ACL will cause setacl attempt on a non-directory file to fail.\n" +); + +PyDoc_STRVAR(py_ace_who__doc__, +"tuple : tuple containing information about to whom the ACL entry applies.\n" +"(, ).\n\n" +"Values - whotype\n" +"----------------\n" +"zfsacl.WHOTYPE_USER_OBJ - The owning user of the file. If this is set, then numeric\n" +"id must be set to -1\n" +"zfsacl.WHOTYPE_GROUP_OBJ - The owning group of the file. If this is set, then\n" +"numeric id must be set to -1\n" +"zfsacl.WHOTYPE_EVERYONE - All users. For NFSv4 ACL brand, this includes the\n" +"file owner and group (as opposed to `other` in conventional POSIX mode)\n" +"zfsacl.WHOTYPE_USER - The numeric ID is a user.\n" +"zfsacl.WHOTYPE_GROUP - The numeric ID is a group.\n" +); + +PyDoc_STRVAR(py_ace_entry_type__doc__, +"int : ACE type. See RFC 5661 Section 6.2.1.1 and relevant operating system\n" +"documentation for more implementation details.\n\n" +"Values\n" +"------\n" +"zfsacl.ENTRY_TYPE_ALLOW - Explicitly grants the access defined in permset\n" +"zfsacl.ENTRY_TYPE_DENY - Explicitly denies the access defined in permset\n" +); +/* END CSTYLED */ + +static PyGetSetDef ace_object_getsetters[] = { + { + .name = "idx", + .get = (getter)ace_get_idx, + .doc = py_ace_idx__doc__, + }, + { + .name = "permset", + .get = (getter)ace_get_permset, + .set = (setter)ace_set_permset, + .doc = py_ace_permset__doc__, + }, + { + .name = "flagset", + .get = (getter)ace_get_flagset, + .set = (setter)ace_set_flagset, + .doc = py_ace_flagset__doc__, + }, + { + .name = "who", + .get = (getter)ace_get_who, + .set = (setter)ace_set_who, + .doc = py_ace_who__doc__, + }, + { + .name = "entry_type", + .get = (getter)ace_get_entry_type, + .set = (setter)ace_set_entry_type, + .doc = py_ace_entry_type__doc__, + }, + { .name = NULL } +}; + +static PyTypeObject PyZfsACLEntry = { + .tp_name = "zfsacl.ACLEntry", + .tp_basicsize = sizeof (py_acl_entry), + .tp_methods = ace_object_methods, + .tp_getset = ace_object_getsetters, + .tp_new = py_ace_new, + .tp_init = py_ace_init, + .tp_doc = "An ACL Entry", + .tp_dealloc = (destructor)py_ace_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, +}; + +static PyObject * +py_acl_new(PyTypeObject *obj, PyObject *args_unused, + PyObject *kwargs_unused) +{ + py_acl *self = NULL; + + self = (py_acl *)obj->tp_alloc(obj, 0); + if (self == NULL) { + return (NULL); + } + self->theacl = NULL; + self->verbose = B_FALSE; + return ((PyObject *)self); +} + +static int +py_acl_init(PyObject *obj, PyObject *args, PyObject *kwargs) +{ + py_acl *self = (py_acl *)obj; + zfsacl_t theacl = NULL; + char *kwnames[] = { "fd", "path", "brand", NULL }; + int fd = 0, brand = ZFSACL_BRAND_NFSV4; + char *path = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|isi", kwnames, + &fd, &path, &brand)) { + return (-1); + } + + if (fd != 0) { + theacl = zfsacl_get_fd(fd, brand); + if (theacl == NULL) { + set_exc_from_errno("zfsacl_get_fd()"); + return (-1); + } + } else if (path != NULL) { + theacl = zfsacl_get_file(path, brand); + if (theacl == NULL) { + set_exc_from_errno("zfsacl_get_file()"); + return (-1); + } + } else { + theacl = zfsacl_init(ZFSACL_MAX_ENTRIES, brand); + if (theacl == NULL) { + set_exc_from_errno("zfsacl_get_file()"); + return (-1); + } + } + + if (theacl == NULL) { + set_exc_from_errno("zfsace_set_entry_type()"); + return (-1); + } + + self->theacl = theacl; + + return (0); +} + +static void +py_acl_dealloc(py_acl *self) +{ + if (self->theacl != NULL) { + zfsacl_free(&self->theacl); + self->theacl = NULL; + } + Py_TYPE(self)->tp_free((PyObject *)self); +} + +static PyObject * +acl_get_verbose(PyObject *obj, void *closure) +{ + py_acl *self = (py_acl *)obj; + + if (self->verbose) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; +} + +static int +acl_set_verbose(PyObject *obj, PyObject *value, void *closure) +{ + py_acl *self = (py_acl *)obj; + + if (!PyBool_Check(value)) { + PyErr_SetString(PyExc_TypeError, "value must be boolean."); + return (-1); + } + + self->verbose = (value == Py_True) ? B_TRUE : B_FALSE; + return (0); +} + +static PyObject * +acl_get_flags(PyObject *obj, void *closure) +{ + py_acl *self = (py_acl *)obj; + boolean_t ok; + zfsacl_aclflags_t flags; + PyObject *out = NULL; + + ok = zfsacl_get_aclflags(self->theacl, &flags); + if (!ok) { + set_exc_from_errno("zfsacl_get_aclflags()"); + return (NULL); + } + + out = Py_BuildValue("I", flags); + return (out); +} + +static int +acl_set_flags(PyObject *obj, PyObject *value, void *closure) +{ + py_acl *self = (py_acl *)obj; + long val; + boolean_t ok; + + if (!PyLong_Check(value)) { + PyErr_SetString(PyExc_TypeError, "flags must be integer"); + return (-1); + } + + val = PyLong_AsLong(value); + + if (ZFSACL_FLAGS_INVALID(val)) { + PyErr_SetString(PyExc_ValueError, + "Invalid ACL flags specified"); + return (-1); + } + + ok = zfsacl_set_aclflags(self->theacl, (zfsacl_aclflags_t)val); + if (!ok) { + set_exc_from_errno("zfsacl_set_aclflags()"); + return (-1); + } + + return (0); +} + +static PyObject * +acl_get_brand(PyObject *obj, void *closure) +{ + py_acl *self = (py_acl *)obj; + boolean_t ok; + zfsacl_brand_t brand; + PyObject *out = NULL; + + ok = zfsacl_get_brand(self->theacl, &brand); + if (!ok) { + set_exc_from_errno("zfsacl_get_brand()"); + return (NULL); + } + + out = Py_BuildValue("I", brand); + return (out); +} + +static PyObject * +acl_get_acecnt(PyObject *obj, void *closure) +{ + py_acl *self = (py_acl *)obj; + boolean_t ok; + uint_t acecnt; + PyObject *out = NULL; + + ok = zfsacl_get_acecnt(self->theacl, &acecnt); + if (!ok) { + set_exc_from_errno("zfsacl_get_acecnt()"); + return (NULL); + } + + out = Py_BuildValue("I", acecnt); + return (out); +} + +static boolean_t +initialize_py_ace(py_acl *self, PyObject *in, int idx, + zfsacl_entry_t entry) +{ + py_acl_entry *out = (py_acl_entry *)in; + boolean_t ok; + uint_t acecnt; + + ok = zfsacl_get_acecnt(self->theacl, &acecnt); + if (!ok) { + set_exc_from_errno("zfsacl_get_acecnt()"); + return (B_FALSE); + } + + out->theace = entry; + out->parent_acl = self; + out->initial_cnt = acecnt; + out->idx = (idx == ZFSACL_APPEND_ENTRY) ? (int)acecnt : idx; + Py_INCREF(out->parent_acl); + return (B_TRUE); +} + +static boolean_t +pyargs_get_index(py_acl *self, PyObject *args, int *pidx, + boolean_t required) +{ + int val = -1; + boolean_t ok; + uint_t acecnt; + const char *format = required ? "i" : "|i"; + + if (!PyArg_ParseTuple(args, format, &val)) + return (B_FALSE); + + if (val == -1) { + *pidx = ZFSACL_APPEND_ENTRY; + return (B_TRUE); + } else if (val == 0) { + *pidx = 0; + return (B_TRUE); + } + + if (val < 0) { + PyErr_SetString(PyExc_ValueError, "Index may not be negative"); + return (B_FALSE); + } + + if (val > (ZFSACL_MAX_ENTRIES -1)) { + PyErr_SetString(PyExc_ValueError, + "Index exceeds maximum entries for ACL"); + return (B_FALSE); + } + + ok = zfsacl_get_acecnt(self->theacl, &acecnt); + if (!ok) { + set_exc_from_errno("zfsacl_get_acecnt()"); + return (B_FALSE); + } + + if ((acecnt == 0) || (((uint_t)val) > acecnt -1)) { + PyErr_Format(PyExc_IndexError, + "%ld: index invalid, ACL contains (%u) entries.", val, + acecnt); + return (B_FALSE); + } + + if (val > (ZFSACL_MAX_ENTRIES -1)) { + PyErr_SetString(PyExc_ValueError, + "Index exceeds maximum entries for ACL"); + return (B_FALSE); + } + + *pidx = val; + return (B_TRUE); +} + +/* BEGIN CSTYLED */ +PyDoc_STRVAR(py_acl_create_entry__doc__, +"create_entry(index)\n" +"--\n\n" +"Create a new ACL entry. If index is unspecified then entry\n" +"will be appended to ACL.\n\n" +"Parameters\n" +"----------\n" +"index : int, optional\n" +" Position of new entry in ACL.\n\n" +"Returns\n" +"-------\n" +" new zfsacl.ACLEntry object\n" +); +/* END CSTYLED */ + +static PyObject * +py_acl_create_entry(PyObject *obj, PyObject *args) +{ + py_acl *self = (py_acl *)obj; + boolean_t ok; + int idx; + zfsacl_entry_t entry = NULL; + PyObject *pyentry = NULL; + + ok = pyargs_get_index(self, args, &idx, B_FALSE); + if (!ok) { + return (NULL); + } + + ok = zfsacl_create_aclentry(self->theacl, idx, &entry); + if (!ok) { + set_exc_from_errno("zfsacl_create_aclentry()"); + return (NULL); + } + + pyentry = PyObject_CallFunction((PyObject *)&PyZfsACLEntry, NULL); + ok = initialize_py_ace(self, pyentry, idx, entry); + if (!ok) { + Py_CLEAR(pyentry); + return (NULL); + } + + return ((PyObject *)pyentry); +} + +/* BEGIN CSTYLED */ +PyDoc_STRVAR(py_acl_get_entry__doc__, +"get_entry(index)\n" +"--\n\n" +"Retrieve ACL entry with specified index from ACL.\n\n" +"Parameters\n" +"----------\n" +"index : int\n" +" Position of entry in ACL to be retrieved.\n\n" +"Returns\n" +"-------\n" +" new zfsacl.ACLEntry object\n" +); +/* END CSTYLED */ + +static PyObject * +py_acl_get_entry(PyObject *obj, PyObject *args) +{ + py_acl *self = (py_acl *)obj; + boolean_t ok; + int idx; + zfsacl_entry_t entry = NULL; + PyObject *pyentry = NULL; + + ok = pyargs_get_index(self, args, &idx, B_TRUE); + if (!ok) { + return (NULL); + } + + ok = zfsacl_get_aclentry(self->theacl, idx, &entry); + if (!ok) { + set_exc_from_errno("zfsacl_get_aclentry()"); + return (NULL); + } + + pyentry = PyObject_CallFunction((PyObject *)&PyZfsACLEntry, NULL); + ok = initialize_py_ace(self, pyentry, idx, entry); + if (!ok) { + Py_CLEAR(pyentry); + return (NULL); + } + + return ((PyObject *)pyentry); +} + +/* BEGIN CSTYLED */ +PyDoc_STRVAR(py_acl_delete_entry__doc__, +"delete_entry(index)\n" +"--\n\n" +"Remove the ACL entry specified by index from the ACL.\n\n" +"Parameters\n" +"----------\n" +"index : int\n" +" Position of entry in ACL to be removed.\n\n" +"Returns\n" +"-------\n" +" None\n" +); +/* END CSTYLED */ + +static PyObject * +py_acl_delete_entry(PyObject *obj, PyObject *args) +{ + py_acl *self = (py_acl *)obj; + boolean_t ok; + int idx; + + ok = pyargs_get_index(self, args, &idx, B_TRUE); + if (!ok) { + return (NULL); + } + + ok = zfsacl_delete_aclentry(self->theacl, idx); + if (!ok) { + if ((errno == ERANGE) && (idx == 0)) { + PyErr_SetString(PyExc_ValueError, + "At least one ACL entry is required."); + return (NULL); + } + set_exc_from_errno("zfsacl_delete_aclentry()"); + return (NULL); + } + + Py_RETURN_NONE; +} + +/* BEGIN CSTYLED */ +PyDoc_STRVAR(py_acl_set__doc__, +"setacl(fd=-1, path=None)\n" +"--\n\n" +"Set the acl on either a path or open file.\n" +"Either a path or file must be specified (not both).\n\n" +"Parameters\n" +"----------\n" +"fd : int, optional\n" +" Open file descriptor to use for setting ACL.\n" +"path : string, optional\n" +" Path of file on which to set ACL.\n\n" +"Returns\n" +"-------\n" +" None\n" +); +/* END CSTYLED */ + +static PyObject * +py_acl_set(PyObject *obj, PyObject *args, PyObject *kwargs) +{ + py_acl *self = (py_acl *)obj; + boolean_t ok; + int fd = -1; + const char *path = NULL; + char *kwnames [] = { "fd", "path", NULL }; + + ok = PyArg_ParseTupleAndKeywords(args, kwargs, "|is", kwnames, &fd, + &path); + + if (!ok) { + return (NULL); + } + + if (fd != -1) { + ok = zfsacl_set_fd(fd, self->theacl); + if (!ok) { + set_exc_from_errno("zfsacl_set_fd()"); + return (NULL); + } + } else if (path != NULL) { + ok = zfsacl_set_file(path, self->theacl); + if (!ok) { + set_exc_from_errno("zfsacl_set_file()"); + return (NULL); + } + } else { + PyErr_SetString(PyExc_ValueError, + "`fd` or `path` key is required"); + } + + Py_RETURN_NONE; +} + +static PyObject * +py_acl_iter(PyObject *obj, PyObject *args_unused) +{ + py_acl *self = (py_acl *)obj; + py_acl_iterator *out = NULL; + + out = PyObject_New(py_acl_iterator, &PyACLIterator); + if (out == NULL) { + return (NULL); + } + + out->current_idx = 0; + out->acl = self; + Py_INCREF(self); + return ((PyObject *)out); +} + +static PyMethodDef acl_object_methods[] = { + { + .ml_name = "setacl", + .ml_meth = (PyCFunction)py_acl_set, + .ml_flags = METH_VARARGS|METH_KEYWORDS, + .ml_doc = py_acl_set__doc__ + }, + { + .ml_name = "create_entry", + .ml_meth = py_acl_create_entry, + .ml_flags = METH_VARARGS, + .ml_doc = py_acl_create_entry__doc__ + }, + { + .ml_name = "get_entry", + .ml_meth = py_acl_get_entry, + .ml_flags = METH_VARARGS, + .ml_doc = py_acl_get_entry__doc__ + }, + { + .ml_name = "delete_entry", + .ml_meth = py_acl_delete_entry, + .ml_flags = METH_VARARGS, + .ml_doc = py_acl_delete_entry__doc__ + }, + { + .ml_name = "calculate_inherited_acl", + .ml_meth = (PyCFunction)py_acl_inherit, + .ml_flags = METH_VARARGS|METH_KEYWORDS, + .ml_doc = "calculate an inherited ACL" + }, + { NULL, NULL, 0, NULL } +}; + +/* BEGIN CSTYLED */ +PyDoc_STRVAR(py_acl_verbose__doc__, +"bool : Attribute controls whether information about the ACL\n" +"will be printed in verbose format.\n" +); + +PyDoc_STRVAR(py_acl_flags__doc__, +"int : ACL-wide flags. For description of flags see RFC-5661\n" +"section 6.4.2.3 - Automatic Inheritance.\n\n" +"These flags are interpreted by client applications (for example \n" +"Samba) and should be evaluated by applications that recursively\n" +"manage ACLs.\n\n" +"Examples: zfsacl.AUTO_INHERIT, zfsacl.PROTECTED\n" +); + +PyDoc_STRVAR(py_acl_brand__doc__, +"read-only attribute indicating the brand of ACL (POSIX1E or NFSv4).\n" +); + +PyDoc_STRVAR(py_acl_ace_count__doc__, +"read-only attribute indicating the number of ACEs in the ACL.\n" +); +/* END CSTYLED */ + +static PyGetSetDef acl_object_getsetters[] = { + { + .name = "verbose_output", + .get = (getter)acl_get_verbose, + .set = (setter)acl_set_verbose, + .doc = py_acl_verbose__doc__, + }, + { + .name = "acl_flags", + .get = (getter)acl_get_flags, + .set = (setter)acl_set_flags, + .doc = py_acl_flags__doc__, + }, + { + .name = "brand", + .get = (getter)acl_get_brand, + .doc = py_acl_brand__doc__, + }, + { + .name = "ace_count", + .get = (getter)acl_get_acecnt, + .doc = py_acl_ace_count__doc__, + }, + { .name = NULL } +}; + +static PyTypeObject PyZfsACL = { + .tp_name = "zfsacl.ACL", + .tp_basicsize = sizeof (py_acl), + .tp_methods = acl_object_methods, + .tp_getset = acl_object_getsetters, + .tp_new = py_acl_new, + .tp_init = py_acl_init, + .tp_doc = "An ACL", + .tp_dealloc = (destructor)py_acl_dealloc, + .tp_iter = (getiterfunc)py_acl_iter, + .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_ITER, +}; + +static PyObject * +py_acl_inherit(PyObject *obj, PyObject *args, PyObject *kwargs) +{ + py_acl *self = (py_acl *)obj, *out = NULL; + boolean_t ok, isdir = B_TRUE; + zfsacl_t parent, result; + + char *kwnames [] = { "is_dir", NULL }; + + ok = PyArg_ParseTupleAndKeywords(args, kwargs, "|bO", kwnames, + &isdir); + + if (!ok) { + return (NULL); + } + + parent = self->theacl; + + result = zfsacl_calculate_inherited_acl(parent, NULL, isdir); + if (result == NULL) { + return (NULL); + } + + out = (py_acl *)PyObject_CallFunction((PyObject *)&PyZfsACL, NULL); + if (out == NULL) { + return (NULL); + } + + out->theacl = result; + return ((PyObject *)out); +} + +static PyMethodDef acl_module_methods[] = { + { .ml_name = NULL } +}; +#define MODULE_DOC "ZFS ACL python bindings." + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + .m_name = "zfsacl", + .m_doc = MODULE_DOC, + .m_size = -1, + .m_methods = acl_module_methods, +}; + +PyObject* +module_init(void) +{ + PyObject *m = NULL; + m = PyModule_Create(&moduledef); + if (m == NULL) { + fprintf(stderr, "failed to initalize module\n"); + return (NULL); + } + + if (PyType_Ready(&PyZfsACL) < 0) + return (NULL); + + if (PyType_Ready(&PyZfsACLEntry) < 0) + return (NULL); + + /* ZFS ACL branding */ + PyModule_AddIntConstant(m, "BRAND_UNKNOWN", ZFSACL_BRAND_UNKNOWN); + PyModule_AddIntConstant(m, "BRAND_ACCESS", ZFSACL_BRAND_ACCESS); + PyModule_AddIntConstant(m, "BRAND_DEFAULT", ZFSACL_BRAND_DEFAULT); + PyModule_AddIntConstant(m, "BRAND_NFSV4", ZFSACL_BRAND_NFSV4); + + /* ZFS ACL whotypes */ + PyModule_AddIntConstant(m, "WHOTYPE_UNDEFINED", ZFSACL_UNDEFINED_TAG); + PyModule_AddIntConstant(m, "WHOTYPE_USER_OBJ", ZFSACL_USER_OBJ); + PyModule_AddIntConstant(m, "WHOTYPE_GROUP_OBJ", ZFSACL_GROUP_OBJ); + PyModule_AddIntConstant(m, "WHOTYPE_EVERYONE", ZFSACL_EVERYONE); + PyModule_AddIntConstant(m, "WHOTYPE_USER", ZFSACL_USER); + PyModule_AddIntConstant(m, "WHOTYPE_GROUP", ZFSACL_GROUP); + PyModule_AddIntConstant(m, "WHOTYPE_MASK", ZFSACL_MASK); + + /* ZFS ACL entry types */ + PyModule_AddIntConstant(m, "ENTRY_TYPE_ALLOW", ZFSACL_ENTRY_TYPE_ALLOW); + PyModule_AddIntConstant(m, "ENTRY_TYPE_DENY", ZFSACL_ENTRY_TYPE_DENY); + + /* ZFS ACL ACL-wide flags */ + PyModule_AddIntConstant(m, "ACL_AUTO_INHERIT", ZFSACL_AUTO_INHERIT); + PyModule_AddIntConstant(m, "ACL_PROTECTED", ZFSACL_PROTECTED); + PyModule_AddIntConstant(m, "ACL_DEFAULT", ZFSACL_DEFAULTED); + + /* valid on get, but not set */ + PyModule_AddIntConstant(m, "ACL_IS_TRIVIAL", ZFSACL_IS_TRIVIAL); + + /* ZFS ACL inherit flags (NFSv4 only) */ + PyModule_AddIntConstant(m, "FLAG_FILE_INHERIT", ZFSACE_FILE_INHERIT); + PyModule_AddIntConstant(m, "FLAG_DIRECTORY_INHERIT", + ZFSACE_DIRECTORY_INHERIT); + PyModule_AddIntConstant(m, "FLAG_NO_PROPAGATE_INHERIT", + ZFSACE_NO_PROPAGATE_INHERIT); + PyModule_AddIntConstant(m, "FLAG_INHERIT_ONLY", ZFSACE_INHERIT_ONLY); + PyModule_AddIntConstant(m, "FLAG_INHERITED", ZFSACE_INHERITED_ACE); + + /* ZFS ACL permissions */ + /* POSIX1e and NFSv4 */ + PyModule_AddIntConstant(m, "PERM_READ_DATA", ZFSACE_READ_DATA); + PyModule_AddIntConstant(m, "PERM_WRITE_DATA", ZFSACE_WRITE_DATA); + PyModule_AddIntConstant(m, "PERM_EXECUTE", ZFSACE_EXECUTE); + + /* NFSv4 only */ + PyModule_AddIntConstant(m, "PERM_LIST_DIRECTORY", + ZFSACE_LIST_DIRECTORY); + PyModule_AddIntConstant(m, "PERM_ADD_FILE", ZFSACE_ADD_FILE); + PyModule_AddIntConstant(m, "PERM_APPEND_DATA", ZFSACE_APPEND_DATA); + PyModule_AddIntConstant(m, "PERM_ADD_SUBDIRECTORY", + ZFSACE_ADD_SUBDIRECTORY); + PyModule_AddIntConstant(m, "PERM_READ_NAMED_ATTRS", + ZFSACE_READ_NAMED_ATTRS); + PyModule_AddIntConstant(m, "PERM_WRITE_NAMED_ATTRS", + ZFSACE_WRITE_NAMED_ATTRS); + PyModule_AddIntConstant(m, "PERM_DELETE_CHILD", ZFSACE_DELETE_CHILD); + PyModule_AddIntConstant(m, "PERM_READ_ATTRIBUTES", + ZFSACE_READ_ATTRIBUTES); + PyModule_AddIntConstant(m, "PERM_WRITE_ATTRIBUTES", + ZFSACE_WRITE_ATTRIBUTES); + PyModule_AddIntConstant(m, "PERM_DELETE", ZFSACE_DELETE); + PyModule_AddIntConstant(m, "PERM_READ_ACL", ZFSACE_READ_ACL); + PyModule_AddIntConstant(m, "PERM_WRITE_ACL", ZFSACE_WRITE_ACL); + PyModule_AddIntConstant(m, "PERM_WRITE_OWNER", ZFSACE_WRITE_OWNER); + PyModule_AddIntConstant(m, "PERM_SYNCHRONIZE", ZFSACE_SYNCHRONIZE); + PyModule_AddIntConstant(m, "BASIC_PERM_FULL_CONTROL", ZFSACE_FULL_SET); + PyModule_AddIntConstant(m, "BASIC_PERM_MODIFY", ZFSACE_MODIFY_SET); + PyModule_AddIntConstant(m, "BASIC_PERM_READ", + ZFSACE_READ_SET | ZFSACE_EXECUTE); + PyModule_AddIntConstant(m, "BASIC_PERM_TRAVERSE", ZFSACE_TRAVERSE_SET); + + PyModule_AddObject(m, "Acl", (PyObject *)&PyZfsACL); + + return (m); +} + +PyMODINIT_FUNC +PyInit_libzfsacl(void) +{ + return (module_init()); +} diff --git a/lib/libzfsacl/setup.py.in b/lib/libzfsacl/setup.py.in new file mode 100644 index 000000000000..ae979a93535d --- /dev/null +++ b/lib/libzfsacl/setup.py.in @@ -0,0 +1,26 @@ +from setuptools import setup, Extension, find_packages +import os +import sys + +srcdir = '@abs_top_srcdir@/lib/libzfsacl' + +topsrcdir = '@abs_top_srcdir@' +incdir = [topsrcdir, topsrcdir + '/include', topsrcdir + '/lib/libspl/include'] +src = ['zfsacl/libzfsacl_impl_common.c', 'libpyzfsacl.c'] +if sys.platform.startswith('linux'): + src.append('zfsacl/libzfsacl_impl_linux.c') +else: + src.append('zfsacl/libzfsacl_impl_freebsd.c') + +libzfsacl_mod = Extension('libzfsacl', include_dirs=incdir, sources=src) +setup( + name='libzfsacl', + version='@VERSION@', + description='ACL wrapper library for acessing NFSv4 ACLs on Linux/FreeBSD', + ext_modules=[libzfsacl_mod], + packages=find_packages(where=srcdir), + package_dir={"": os.path.relpath(srcdir)}, + include_package_data=True, + python_requires='>=3.6,<4', + zip_safe=False, +) diff --git a/lib/libzfsacl/sunacl/Makefile.am b/lib/libzfsacl/sunacl/Makefile.am new file mode 100644 index 000000000000..88a5760919c4 --- /dev/null +++ b/lib/libzfsacl/sunacl/Makefile.am @@ -0,0 +1,22 @@ +libsunacl_la_CFLAGS = $(AM_CFLAGS) $(LIBRARY_CFLAGS) + +lib_LTLIBRARIES += libsunacl.la +CPPCHECKTARGETS += libsunacl.la + +libsunacl_la_CPPFLAGS = $(AM_CPPFLAGS) + +dist_libsunacl_la_SOURCES = \ + %D%/libsunacl.c + +libsunacl_la_LIBADD = \ + libzfsacl.la \ + libzfs.la + +libsunacl_la_LDFLAGS = + +if !ASAN_ENABLED +libsunacl_la_LDFLAGS += -Wl,-z,defs +endif + +libsunacl_la_LDFLAGS += -version-info 1:0:0 + diff --git a/lib/libzfsacl/sunacl/libsunacl.c b/lib/libzfsacl/sunacl/libsunacl.c new file mode 100644 index 000000000000..09ffe78afa40 --- /dev/null +++ b/lib/libzfsacl/sunacl/libsunacl.c @@ -0,0 +1,333 @@ +/* + * 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 https://opensource.org/licenses/CDDL-1.0. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright (c) 2008, 2009 Edward Tomasz NapieraƂa + * Copyright (c) 2022 Andrew Walker + * All rights reserved. + */ + +#include +#include +#include +#include +#include + + +#define ACE_GETACL 4 +#define ACE_SETACL 5 +#define ACE_GETACLCNT 6 + +static int +acl_from_aces(zfsacl_t aclp, const ace_t *aces, int nentries) +{ + int i; + const ace_t *ace = NULL; + boolean_t ok; + + if (nentries > ZFSACL_MAX_ENTRIES) { + /* + * I believe it may happen only when moving a pool + * from SunOS to FreeBSD. + */ + printf("acl_from_aces: ZFS ACL too big to fit " + "into 'struct acl'; returning EINVAL.\n"); + return (EINVAL); + } + + for (i = 0; i < nentries; i++) { + zfsace_permset_t permset = 0; + zfsace_flagset_t flagset = 0; + zfsace_who_t whotype = 0; + zfsace_id_t whoid = ZFSACL_UNDEFINED_ID; + zfsace_entry_type_t entry_type = 0; + zfsacl_entry_t entry = NULL; + + ok = zfsacl_get_aclentry(aclp, i, &entry); + if (!ok) { + return (errno); + } + + ace = &(aces[i]); + + permset = ace->a_access_mask; + flagset = ace->a_flags; + + if (ace->a_flags & ACE_OWNER) { + whotype = ZFSACL_USER_OBJ; + } else if (ace->a_flags & ACE_GROUP) { + whotype = ZFSACL_GROUP_OBJ; + flagset |= ZFSACE_IDENTIFIER_GROUP; + } else if (ace->a_flags & ACE_EVERYONE) { + whotype = ZFSACL_EVERYONE; + } else if (ace->a_flags & ACE_IDENTIFIER_GROUP) { + whotype = ZFSACL_GROUP; + flagset |= ZFSACE_IDENTIFIER_GROUP; + } else { + whotype = ZFSACL_USER; + } + + if (whotype == ZFSACL_USER || whotype == ZFSACL_GROUP) + whoid = ace->a_who; + + switch (ace->a_type) { + case ACE_ACCESS_ALLOWED_ACE_TYPE: + entry_type = ZFSACL_ENTRY_TYPE_ALLOW; + break; + case ACE_ACCESS_DENIED_ACE_TYPE: + entry_type = ZFSACL_ENTRY_TYPE_DENY; + break; + case ACE_SYSTEM_AUDIT_ACE_TYPE: + entry_type = ZFSACL_ENTRY_TYPE_AUDIT; + break; + case ACE_SYSTEM_ALARM_ACE_TYPE: + entry_type = ZFSACL_ENTRY_TYPE_ALARM; + break; + default: + abort(); + } + + ok = zfsace_set_permset(entry, permset); + if (!ok) { + return (errno); + } + ok = zfsace_set_flagset(entry, flagset); + if (!ok) { + return (errno); + } + ok = zfsace_set_who(entry, whotype, whoid); + if (!ok) { + return (errno); + } + ok = zfsace_set_entry_type(entry, entry_type); + if (!ok) { + return (errno); + } + } + + return (0); +} + +static int +aces_from_acl(ace_t *aces, int *nentries, zfsacl_t aclp) +{ + int i; + uint_t acecnt; + ace_t *ace; + boolean_t ok; + + ok = zfsacl_get_acecnt(aclp, &acecnt); + if (!ok) { + return (errno); + } + + memset(aces, 0, sizeof (*aces) * acecnt); + *nentries = (int)acecnt; + + for (i = 0; i < (int)acecnt; i++) { + zfsace_permset_t permset = 0; + zfsace_flagset_t flagset = 0; + zfsace_who_t whotype = 0; + zfsace_id_t whoid = ZFSACL_UNDEFINED_ID; + zfsace_entry_type_t entry_type = 0; + zfsacl_entry_t entry = NULL; + + ok = zfsacl_get_aclentry(aclp, i, &entry); + if (!ok) { + return (errno); + } + ok = zfsace_get_permset(entry, &permset); + if (!ok) { + return (errno); + } + ok = zfsace_get_flagset(entry, &flagset); + if (!ok) { + return (errno); + } + ok = zfsace_get_who(entry, &whotype, &whoid); + if (!ok) { + return (errno); + } + ok = zfsace_get_entry_type(entry, &entry_type); + if (!ok) { + return (errno); + } + + ace = &(aces[i]); + + ace->a_who = whoid; + ace->a_access_mask = permset; + ace->a_flags = flagset; + + if (whotype == ZFSACL_USER_OBJ) + ace->a_flags |= ACE_OWNER; + else if (whotype == ZFSACL_GROUP_OBJ) + ace->a_flags |= (ACE_GROUP | ACE_IDENTIFIER_GROUP); + else if (whotype == ZFSACL_GROUP) + ace->a_flags |= ACE_IDENTIFIER_GROUP; + else if (whotype == ZFSACL_EVERYONE) + ace->a_flags |= ACE_EVERYONE; + + switch (entry_type) { + case ZFSACL_ENTRY_TYPE_ALLOW: + ace->a_type = ACE_ACCESS_ALLOWED_ACE_TYPE; + break; + case ZFSACL_ENTRY_TYPE_DENY: + ace->a_type = ACE_ACCESS_DENIED_ACE_TYPE; + break; + case ZFSACL_ENTRY_TYPE_ALARM: + ace->a_type = ACE_SYSTEM_ALARM_ACE_TYPE; + break; + case ZFSACL_ENTRY_TYPE_AUDIT: + ace->a_type = ACE_SYSTEM_AUDIT_ACE_TYPE; + break; + default: + abort(); + } + } + + return (0); +} + +static int +xacl(const char *path, int fd, int cmd, int cnt, void *buf) +{ + int error, nentries = 0; + zfsacl_t aclp = NULL; + uint_t acecnt; + boolean_t ok; + + switch (cmd) { + case ACE_SETACL: + if (buf == NULL || cnt <= 0) { + errno = EINVAL; + return (-1); + } + + if (cnt >= ZFSACL_MAX_ENTRIES) { + errno = ENOSPC; + return (-1); + } + + aclp = zfsacl_init(cnt, ZFSACL_BRAND_NFSV4); + if (aclp == NULL) { + return (-1); + } + + error = acl_from_aces(aclp, buf, cnt); + if (error) { + zfsacl_free(&aclp); + errno = EIO; + return (-1); + } + + /* + * Ugly hack to make sure we don't trip sanity check at + * lib/libc/posix1e/acl_branding.c: + * _acl_type_not_valid_for_acl(). + */ + if (path != NULL) + ok = zfsacl_set_file(path, aclp); + else + ok = zfsacl_set_fd(fd, aclp); + if (error) { + if (errno == EOPNOTSUPP || errno == EINVAL) + errno = ENOSYS; + zfsacl_free(&aclp); + return (-1); + } + + zfsacl_free(&aclp); + return (0); + + case ACE_GETACL: + if (buf == NULL) { + errno = EINVAL; + return (-1); + } + + if (path != NULL) + aclp = zfsacl_get_file(path, ZFSACL_BRAND_NFSV4); + else + aclp = zfsacl_get_fd(fd, ZFSACL_BRAND_NFSV4); + if (aclp == NULL) { + if (errno == EOPNOTSUPP || errno == EINVAL) + errno = ENOSYS; + return (-1); + } + + ok = zfsacl_get_acecnt(aclp, &acecnt); + if (!ok || acecnt > (uint_t)cnt) { + zfsacl_free(&aclp); + errno = ENOSPC; + return (-1); + } + + error = aces_from_acl(buf, &nentries, aclp); + zfsacl_free(&aclp); + if (error) { + errno = EIO; + return (-1); + } + + return (nentries); + + case ACE_GETACLCNT: + if (path != NULL) + aclp = zfsacl_get_file(path, ZFSACL_BRAND_NFSV4); + else + aclp = zfsacl_get_fd(fd, ZFSACL_BRAND_NFSV4); + if (aclp == NULL) { + if (errno == EOPNOTSUPP || errno == EINVAL) + errno = ENOSYS; + return (-1); + } + + ok = zfsacl_get_acecnt(aclp, &acecnt); + if (!ok) { + return (-1); + } + nentries = acecnt; + zfsacl_free(&aclp); + return (nentries); + + default: + errno = EINVAL; + return (-1); + } +} + +int +acl(const char *path, int cmd, int cnt, void *buf) +{ + if (path == NULL) { + errno = EINVAL; + return (-1); + } + + return (xacl(path, -1, cmd, cnt, buf)); +} + +int +facl(int fd, int cmd, int cnt, void *buf) +{ + return (xacl(NULL, fd, cmd, cnt, buf)); +} diff --git a/lib/libzfsacl/zfsacl/Makefile.am b/lib/libzfsacl/zfsacl/Makefile.am new file mode 100644 index 000000000000..5e85ba203b33 --- /dev/null +++ b/lib/libzfsacl/zfsacl/Makefile.am @@ -0,0 +1,32 @@ +libzfsacl_la_CFLAGS = $(AM_CFLAGS) $(LIBRARY_CFLAGS) -fPIC + +lib_LTLIBRARIES += libzfsacl.la +CPPCHECKTARGETS += libzfsacl.la + +libzfsacl_la_CPPFLAGS = $(AM_CPPFLAGS) + +if BUILD_LINUX +dist_libzfsacl_la_SOURCES = \ + %D%/libzfsacl_impl_common.c \ + %D%/libzfsacl_impl_linux.c +endif + +if BUILD_FREEBSD +dist_libzfsacl_la_SOURCES = \ + %D%/libzfsacl_impl_common.c \ + %D%/libzfsacl_impl_freebsd.c +endif + +libzfsacl_la_LIBADD = \ + libzfs.la \ + libspl.la + +libzfsacl_la_LIBADD += $(LTLIBINTL) + +libzfsacl_la_LDFLAGS = + +if !ASAN_ENABLED +libzfsacl_la_LDFLAGS += -Wl,-z,defs +endif + +libzfsacl_la_LDFLAGS += -version-info 1:0:0 diff --git a/lib/libzfsacl/zfsacl/libzfsacl_impl_common.c b/lib/libzfsacl/zfsacl/libzfsacl_impl_common.c new file mode 100644 index 000000000000..2c63756bdbf9 --- /dev/null +++ b/lib/libzfsacl/zfsacl/libzfsacl_impl_common.c @@ -0,0 +1,224 @@ +#include + +#define INHERITANCE_FLAGS ZFSACE_FILE_INHERIT | \ + ZFSACE_DIRECTORY_INHERIT | \ + ZFSACE_NO_PROPAGATE_INHERIT + +static boolean_t +copy_ace(zfsacl_entry_t target, zfsacl_entry_t source, + zfsace_flagset_t new_flags) +{ + zfsace_permset_t aeperms; + zfsace_who_t aewho; + zfsace_id_t aeid; + zfsace_entry_type_t aetp; + + if (!zfsace_get_permset(source, &aeperms)) { + return (B_FALSE); + } + + if (!zfsace_get_who(source, &aewho, &aeid)) { + return (B_FALSE); + } + + if (!zfsace_get_entry_type(source, &aetp)) { + return (B_FALSE); + } + + if (!zfsace_set_permset(target, aeperms)) { + return (B_FALSE); + } + + if (!zfsace_set_who(target, aewho, aeid)) { + return (B_FALSE); + } + + if (!zfsace_set_entry_type(target, aetp)) { + return (B_FALSE); + } + + if (!zfsace_set_flagset(target, new_flags)) { + return (B_FALSE); + } + + return (B_TRUE); +} + +static boolean_t +add_non_inherited_entries(zfsacl_t target, zfsacl_t source) +{ + uint_t i, cnt; + + if (!zfsacl_get_acecnt(source, &cnt)) { + return (B_FALSE); + } + + for (i = 0; i < cnt; i++) { + zfsacl_entry_t ae = NULL; + zfsacl_entry_t new = NULL; + zfsace_flagset_t flags = 0; + + if (!zfsacl_get_aclentry(source, i, &ae)) { + return (B_FALSE); + } + + if (!zfsace_get_flagset(ae, &flags)) { + return (B_FALSE); + } + + if (flags & ZFSACE_INHERITED_ACE) { + continue; + } + + if (!zfsacl_create_aclentry(target, ZFSACL_APPEND_ENTRY, + &new)) { + return (B_FALSE); + } + + if (!copy_ace(new, ae, flags)) { + return (B_FALSE); + } + } + + return (B_TRUE); +} + +static boolean_t +add_inherited_ace(zfsacl_t target, zfsacl_entry_t ae, + zfsace_flagset_t flags, boolean_t isdir) +{ + zfsacl_entry_t new = NULL; + + if (!zfsacl_create_aclentry(target, ZFSACL_APPEND_ENTRY, + &new)) { + return (B_FALSE); + } + + if (isdir) { + if (flags & ZFSACE_INHERIT_ONLY) { + flags &= ~ZFSACE_INHERIT_ONLY; + } else if (flags & ZFSACE_NO_PROPAGATE_INHERIT) { + flags &= ~INHERITANCE_FLAGS; + } + } else { + flags &= ~(ZFSACE_INHERIT_ONLY | INHERITANCE_FLAGS); + } + + flags |= ZFSACE_INHERITED_ACE; + + return (copy_ace(new, ae, flags)); +} + +static boolean_t +add_inherited_entries(zfsacl_t target, zfsacl_t source, boolean_t isdir) +{ + uint_t i, cnt; + + if (!zfsacl_get_acecnt(source, &cnt)) { + return (B_FALSE); + } + + for (i = 0; i < cnt; i++) { + zfsacl_entry_t ae = NULL; + zfsace_flagset_t flags = 0; + + if (!zfsacl_get_aclentry(source, i, &ae)) { + return (B_FALSE); + } + + if (!zfsace_get_flagset(ae, &flags)) { + return (B_FALSE); + } + + if ((flags & (ZFSACE_FILE_INHERIT | + ZFSACE_DIRECTORY_INHERIT)) == 0) { + // Not inheritable, skip + continue; + } + + if (((flags & ZFSACE_DIRECTORY_INHERIT) == 0) && + isdir) { + /* + * Inheritable only on files and this ACL is for + * a directory. + */ + continue; + } + + if (((flags & ZFSACE_FILE_INHERIT) == 0) && !isdir) { + /* + * Inheritable only on directories and this ACL + * is for a file. + */ + continue; + } + + if (!add_inherited_ace(target, ae, flags, isdir)) { + return (B_FALSE); + } + } + + return (B_TRUE); +} + +/* + * Permissions auto-inheritance is only a NFSv4 ACL feature + */ +static boolean_t acl_may_inherit(zfsacl_t parent, zfsacl_t target) +{ + zfsacl_brand_t brand; + + if (parent == NULL) { + errno = EINVAL; + return (B_FALSE); + } + + if (!zfsacl_get_brand(parent, &brand)) { + return (B_FALSE); + } + + if (brand != ZFSACL_BRAND_NFSV4) { + errno = EOPNOTSUPP; + return (B_FALSE); + } + + if (target) { + if (!zfsacl_get_brand(target, &brand)) { + return (B_FALSE); + } + + + if (brand != ZFSACL_BRAND_NFSV4) { + errno = EOPNOTSUPP; + return (B_FALSE); + } + } + + return (B_TRUE); +} + +zfsacl_t zfsacl_calculate_inherited_acl(zfsacl_t parent, + zfsacl_t target, boolean_t is_dir) +{ + zfsacl_t out = NULL; + + if (!acl_may_inherit(parent, target)) { + return (NULL); + } + + out = zfsacl_init(ZFSACL_MAX_ENTRIES, ZFSACL_BRAND_NFSV4); + + if (target) { + if (!add_non_inherited_entries(out, target)) { + zfsacl_free(&out); + return (NULL); + } + } + + if (!add_inherited_entries(out, parent, is_dir)) { + zfsacl_free(&out); + return (NULL); + } + + return (out); +} diff --git a/lib/libzfsacl/zfsacl/libzfsacl_impl_freebsd.c b/lib/libzfsacl/zfsacl/libzfsacl_impl_freebsd.c new file mode 100644 index 000000000000..d8ef17ae1233 --- /dev/null +++ b/lib/libzfsacl/zfsacl/libzfsacl_impl_freebsd.c @@ -0,0 +1,583 @@ +/* + * 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 https://opensource.org/licenses/CDDL-1.0. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright (c) 2022 Andrew Walker + * All rights reserved. + */ + +#include +#include "/usr/include/sys/acl.h" + +#define BSDACE(zfsace) ((acl_entry_t)zfsace) +#define BSDACL(zfsacl) ((acl_t)zfsacl) +#define ZFSACL(bsdacl) ((zfsacl_t)bsdacl) + +static const struct { + acl_flag_t bsdflag; + zfsace_flagset_t nfs4flag; +} bsdflag2nfs4flag[] = { + { ACL_ENTRY_FILE_INHERIT, ZFSACE_FILE_INHERIT }, + { ACL_ENTRY_DIRECTORY_INHERIT, ZFSACE_DIRECTORY_INHERIT }, + { ACL_ENTRY_NO_PROPAGATE_INHERIT, ZFSACE_NO_PROPAGATE_INHERIT }, + { ACL_ENTRY_INHERIT_ONLY, ZFSACE_INHERIT_ONLY }, + { ACL_ENTRY_INHERITED, ZFSACE_INHERITED_ACE }, +}; + +static const struct { + acl_perm_t bsdperm; + zfsace_permset_t nfs4perm; +} bsdperm2nfs4perm[] = { + { ACL_READ_DATA, ZFSACE_READ_DATA }, + { ACL_WRITE_DATA, ZFSACE_WRITE_DATA }, + { ACL_APPEND_DATA, ZFSACE_APPEND_DATA }, + { ACL_READ_NAMED_ATTRS, ZFSACE_READ_NAMED_ATTRS }, + { ACL_WRITE_NAMED_ATTRS, ZFSACE_WRITE_NAMED_ATTRS }, + { ACL_EXECUTE, ZFSACE_EXECUTE }, + { ACL_DELETE_CHILD, ZFSACE_DELETE_CHILD }, + { ACL_READ_ATTRIBUTES, ZFSACE_READ_ATTRIBUTES }, + { ACL_WRITE_ATTRIBUTES, ZFSACE_WRITE_ATTRIBUTES }, + { ACL_DELETE, ZFSACE_DELETE }, + { ACL_READ_ACL, ZFSACE_READ_ACL }, + { ACL_WRITE_ACL, ZFSACE_WRITE_ACL }, + { ACL_WRITE_OWNER, ZFSACE_WRITE_OWNER }, + { ACL_SYNCHRONIZE, ZFSACE_SYNCHRONIZE }, +}; + +static inline int +CONV_BRAND(zfsacl_brand_t brand_in) +{ + return (brand_in & ACL_BRAND_POSIX) ^ (brand_in & ACL_BRAND_NFS4); +} + +static inline void +BSD_BRAND(acl_t _acl) +{ + _acl->ats_brand = CONV_BRAND(_acl->ats_brand); +} + +static inline acl_type_t +brand_to_type(zfsacl_brand_t _brand) +{ + acl_type_t out = 0; + + switch (_brand) { + case ZFSACL_BRAND_NFSV4: + out = ACL_TYPE_NFS4; + break; + case ZFSACL_BRAND_ACCESS: + out = ACL_TYPE_ACCESS; + break; + case ZFSACL_BRAND_DEFAULT: + out = ACL_TYPE_DEFAULT; + break; + default: + fprintf(stderr, "0x%08x: invalid ACL brand\n", _brand); + break; + }; + + return (out); +} + +zfsacl_t +zfsacl_init(int _acecnt, zfsacl_brand_t _brand) +{ + acl_t out = NULL; + + out = acl_init(_acecnt); + if (out == NULL) { + return (NULL); + } + out->ats_brand = _brand; + return (ZFSACL(out)); +} + +void +zfsacl_free(zfsacl_t *_acl) +{ + acl_t acl = BSDACL(*_acl); + acl_free(acl); + *_acl = NULL; +} + +zfsacl_t +zfsacl_get_fd(int _fd, zfsacl_brand_t _brand) +{ + acl_t out = NULL; + + out = acl_get_fd_np(_fd, brand_to_type(_brand)); + if (out == NULL) { + return (NULL); + } + out->ats_brand = _brand; + return (ZFSACL(out)); +} + +zfsacl_t +zfsacl_get_file(const char *_path_p, zfsacl_brand_t _brand) +{ + acl_t out = NULL; + + out = acl_get_file(_path_p, brand_to_type(_brand)); + if (out == NULL) { + return (NULL); + } + out->ats_brand = _brand; + return (ZFSACL(out)); +} + +zfsacl_t +zfsacl_get_link(const char *_path_p, zfsacl_brand_t _brand) +{ + acl_t out = NULL; + + out = acl_get_link_np(_path_p, brand_to_type(_brand)); + if (out == NULL) { + return (NULL); + } + out->ats_brand = _brand; + return (ZFSACL(out)); +} + +boolean_t +zfsacl_set_fd(int _fd, zfsacl_t _acl) +{ + acl_t acl = BSDACL(_acl); + zfsacl_brand_t saved = acl->ats_brand; + int err; + + BSD_BRAND(acl); + err = acl_set_fd_np(_fd, acl, brand_to_type(saved)); + acl->ats_brand = saved; + + return (err ? B_FALSE : B_TRUE); +} + +boolean_t +zfsacl_set_file(const char *_path_p, zfsacl_t _acl) +{ + acl_t acl = BSDACL(_acl); + zfsacl_brand_t saved = acl->ats_brand; + int err; + + BSD_BRAND(acl); + err = acl_set_file(_path_p, brand_to_type(saved), acl); + acl->ats_brand = saved; + + return (err ? B_FALSE : B_TRUE); +} + +boolean_t +zfsacl_set_link(const char *_path_p, zfsacl_t _acl) +{ + acl_t acl = BSDACL(_acl); + zfsacl_brand_t saved = acl->ats_brand; + int err; + + BSD_BRAND(acl); + err = acl_set_link_np(_path_p, brand_to_type(saved), acl); + acl->ats_brand = saved; + + return (err ? B_FALSE : B_TRUE); +} + +boolean_t +zfsacl_get_brand(zfsacl_t _acl, zfsacl_brand_t *brandp) +{ + acl_t acl = BSDACL(_acl); + *brandp = acl->ats_brand; + return (B_TRUE); +} + +boolean_t +zfsacl_get_aclflags(zfsacl_t _acl, zfsacl_aclflags_t *pflags) +{ + /* + * TODO: FreeBSD still needs to expose ACL flags + * for now we synthesize the PROTECTED flag so that + * Security Descriptor flags can be presented correctly + * to clients. + */ + acl_t acl = BSDACL(_acl); + unsigned int cnt; + zfsace_flagset_t flags_out = 0; + acl_flag_t flags; + + for (cnt = 0; cnt < acl->ats_acl.acl_cnt; cnt++) { + flags = acl->ats_acl.acl_entry[cnt].ae_flags; + if ((flags & ACL_ENTRY_INHERITED) == 0) + continue; + + flags_out = ZFSACL_PROTECTED; + break; + } + + *pflags = flags_out; + return (B_TRUE); +} + + +boolean_t +zfsacl_set_aclflags(zfsacl_t _acl, zfsacl_aclflags_t flags) +{ + /* + * TODO: FreeBSD still needs to expose ACL flags + */ + (void) _acl; + (void) flags; + errno = EOPNOTSUPP; + return (B_FALSE); +} + +boolean_t +zfsacl_get_acecnt(zfsacl_t _acl, uint_t *_acecnt) +{ + acl_t acl = BSDACL(_acl); + *_acecnt = acl->ats_acl.acl_cnt; + return (B_TRUE); +} + +static boolean_t +validate_entry_idx(zfsacl_t _acl, uint_t _idx) +{ + uint_t acecnt; + boolean_t ok; + + ok = zfsacl_get_acecnt(_acl, &acecnt); + if (!ok) { + return (B_FALSE); + } + + if ((_idx + 1) > acecnt) { + errno = E2BIG; + return (B_FALSE); + } + + return (B_TRUE); +} + +boolean_t +zfsacl_create_aclentry(zfsacl_t _acl, int _idx, zfsacl_entry_t *_pentry) +{ + acl_t acl = BSDACL(_acl); + int err; + zfsacl_brand_t saved = acl->ats_brand; + acl_entry_t new_entry = NULL; + + BSD_BRAND(acl); + if (_idx == ZFSACL_APPEND_ENTRY) { + err = acl_create_entry(&acl, &new_entry); + } else { + err = acl_create_entry_np(&acl, &new_entry, _idx); + } + + acl->ats_brand = saved; + + if (err) { + return (B_FALSE); + } + + *_pentry = new_entry; + return (B_TRUE); +} + +boolean_t +zfsacl_get_aclentry(zfsacl_t _acl, int _idx, zfsacl_entry_t *_pentry) +{ + acl_t acl = BSDACL(_acl); + acl_entry_t entry = NULL; + + if (!validate_entry_idx(_acl, _idx)) { + return (B_FALSE); + } + + entry = &acl->ats_acl.acl_entry[_idx]; + *_pentry = entry; + return (B_TRUE); +} + +boolean_t +zfsacl_delete_aclentry(zfsacl_t _acl, int _idx) +{ + acl_t acl = BSDACL(_acl); + int err; + zfsacl_brand_t saved = acl->ats_brand; + + BSD_BRAND(acl); + + err = acl_delete_entry_np(acl, _idx); + acl->ats_brand = saved; + + return (err ? B_FALSE : B_TRUE); +} + +boolean_t +zfsace_get_permset(zfsacl_entry_t _entry, zfsace_permset_t *_pperm) +{ + acl_entry_t entry = BSDACE(_entry); + zfsace_permset_t perm = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(bsdperm2nfs4perm); i++) { + if (entry->ae_perm & bsdperm2nfs4perm[i].bsdperm) { + perm |= bsdperm2nfs4perm[i].nfs4perm; + } + } + + *_pperm = perm; + return (B_TRUE); +} + +boolean_t +zfsace_get_flagset(zfsacl_entry_t _entry, zfsace_flagset_t *_pflags) +{ + acl_entry_t entry = BSDACE(_entry); + zfsace_flagset_t flags = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(bsdflag2nfs4flag); i++) { + if (entry->ae_flags & bsdflag2nfs4flag[i].bsdflag) { + flags |= bsdflag2nfs4flag[i].nfs4flag; + } + } + + if (entry->ae_tag & (ACL_GROUP_OBJ | ACL_GROUP)) { + flags |= ZFSACE_IDENTIFIER_GROUP; + } + + *_pflags = flags; + return (B_TRUE); +} + +boolean_t +zfsace_get_who(zfsacl_entry_t _entry, zfsace_who_t *pwho, zfsace_id_t *_paeid) +{ + acl_entry_t entry = BSDACE(_entry); + zfsace_who_t whotype; + zfsace_id_t whoid = ZFSACL_UNDEFINED_ID; + + switch (entry->ae_tag) { + case ACL_UNDEFINED_TAG: + whotype = ZFSACL_UNDEFINED_TAG; + break; + case ACL_USER_OBJ: + whotype = ZFSACL_USER_OBJ; + break; + case ACL_GROUP_OBJ: + whotype = ZFSACL_GROUP_OBJ; + break; + case ACL_EVERYONE: + whotype = ZFSACL_EVERYONE; + break; + case ACL_MASK: + whotype = ZFSACL_MASK; + break; + case ACL_OTHER: + whotype = ZFSACL_MASK; + break; + case ACL_USER: + whotype = ZFSACL_USER; + whoid = entry->ae_id; + break; + case ACL_GROUP: + whotype = ZFSACL_GROUP; + whoid = entry->ae_id; + break; + default: + abort(); + }; + + *pwho = whotype; + *_paeid = whoid; + return (B_TRUE); +} + +boolean_t +zfsace_get_entry_type(zfsacl_entry_t _entry, zfsace_entry_type_t *_tp) +{ + acl_entry_t entry = BSDACE(_entry); + zfsace_entry_type_t etype; + + switch (entry->ae_entry_type) { + case ACL_ENTRY_TYPE_ALLOW: + etype = ZFSACL_ENTRY_TYPE_ALLOW; + break; + case ACL_ENTRY_TYPE_DENY: + etype = ZFSACL_ENTRY_TYPE_DENY; + break; + case ACL_ENTRY_TYPE_AUDIT: + etype = ZFSACL_ENTRY_TYPE_AUDIT; + break; + case ACL_ENTRY_TYPE_ALARM: + etype = ZFSACL_ENTRY_TYPE_AUDIT; + break; + default: + abort(); + }; + + *_tp = etype; + return (B_TRUE); +} + +boolean_t +zfsace_set_permset(zfsacl_entry_t _entry, zfsace_permset_t _permset) +{ + acl_entry_t entry = BSDACE(_entry); +#if __FreeBSD_version < 1500000 + int permset = 0; +#else + unsigned int permset = 0; +#endif + int i, err; + + for (i = 0; i < ARRAY_SIZE(bsdperm2nfs4perm); i++) { + if (_permset & bsdperm2nfs4perm[i].nfs4perm) { + permset |= bsdperm2nfs4perm[i].bsdperm; + } + } + + err = acl_set_permset(entry, &permset); + return (err ? B_FALSE : B_TRUE); +} + +boolean_t +zfsace_set_flagset(zfsacl_entry_t _entry, zfsace_flagset_t _flagset) +{ + acl_entry_t entry = BSDACE(_entry); + acl_flag_t flags = 0; + int i, err; + + for (i = 0; i < ARRAY_SIZE(bsdflag2nfs4flag); i++) { + if (_flagset & bsdflag2nfs4flag[i].nfs4flag) { + flags |= bsdflag2nfs4flag[i].bsdflag; + } + } + + err = acl_set_flagset_np(entry, &flags); + return (err ? B_FALSE : B_TRUE); +} + +boolean_t +zfsace_set_who(zfsacl_entry_t _entry, zfsace_who_t _who, zfsace_id_t _aeid) +{ + acl_entry_t entry = BSDACE(_entry); + uid_t id = ACL_UNDEFINED_ID; + acl_tag_t tag; + int err; + + switch (_who) { + case ZFSACL_USER_OBJ: + tag = ACL_USER_OBJ; + break; + case ZFSACL_GROUP_OBJ: + tag = ACL_GROUP_OBJ; + break; + case ZFSACL_EVERYONE: + tag = ACL_EVERYONE; + break; + case ZFSACL_OTHER: + tag = ACL_OTHER; + break; + case ZFSACL_MASK: + tag = ACL_MASK; + break; + case ZFSACL_USER: + tag = ACL_USER; + id = _aeid; + break; + case ZFSACL_GROUP: + tag = ACL_GROUP; + id = _aeid; + break; + default: + abort(); + }; + + err = acl_set_tag_type(entry, tag); + if (err) + return (B_FALSE); + + if (id != ACL_UNDEFINED_ID) { + err = acl_set_qualifier(entry, &id); + } + + return (err ? B_FALSE : B_TRUE); +} + +boolean_t +zfsace_set_entry_type(zfsacl_entry_t _entry, zfsace_entry_type_t _tp) +{ + acl_entry_t entry = BSDACE(_entry); + acl_entry_type_t etype; + int err; + + switch (_tp) { + case ZFSACL_ENTRY_TYPE_ALLOW: + etype = ACL_ENTRY_TYPE_ALLOW; + break; + case ZFSACL_ENTRY_TYPE_DENY: + etype = ACL_ENTRY_TYPE_DENY; + break; + case ZFSACL_ENTRY_TYPE_AUDIT: + etype = ACL_ENTRY_TYPE_AUDIT; + break; + case ZFSACL_ENTRY_TYPE_ALARM: + etype = ACL_ENTRY_TYPE_ALARM; + break; + default: + abort(); + }; + + err = acl_set_entry_type_np(entry, etype); + + return (err ? B_FALSE : B_TRUE); +} + +boolean_t +zfsacl_to_native(zfsacl_t _acl, struct native_acl *pnative) +{ + (void) _acl; + (void) pnative; + errno = EOPNOTSUPP; + return (B_FALSE); +} + +boolean_t +zfsacl_is_trivial(zfsacl_t _acl, boolean_t *_trivialp) +{ + acl_t acl = BSDACL(_acl); + int err, triv; + + err = acl_is_trivial_np(acl, &triv); + if (err) { + return (B_FALSE); + } + + *_trivialp = (triv == 1) ? B_TRUE : B_FALSE; + return (B_TRUE); +} + +char * +zfsacl_to_text(zfsacl_t _acl) +{ + acl_t acl = BSDACL(_acl); + return (acl_to_text_np(acl, NULL, ACL_TEXT_NUMERIC_IDS)); +} diff --git a/lib/libzfsacl/zfsacl/libzfsacl_impl_linux.c b/lib/libzfsacl/zfsacl/libzfsacl_impl_linux.c new file mode 100644 index 000000000000..e853d121a26b --- /dev/null +++ b/lib/libzfsacl/zfsacl/libzfsacl_impl_linux.c @@ -0,0 +1,974 @@ +/* + * 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 https://opensource.org/licenses/CDDL-1.0. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright (c) 2022 Andrew Walker + * All rights reserved. + */ + +#include +#include +#include +#include + + +#define ACL4_MAX_ENTRIES ZFSACL_MAX_ENTRIES +#define ACL4_XATTR "system.nfs4_acl_xdr" + +/* Non-ACL metadata */ +#define ACL_GET_SZ(aclp) ((size_t)*(aclp)) + +/* NFSv4 ACL metadata */ +#define ACL4_GET_FL(aclp) (aclp) +#define ACL4_GET_CNT(aclp) (ACL4_GET_FL(aclp) + 1) + +/* NFSv4 ACL ENTRY */ +#define ACE4_SZ (sizeof (uint_t) * 5) +#define ACL4_METADATA (sizeof (uint_t) * 2) +#define ACL4SZ_FROM_ACECNT(cnt) (ACL4_METADATA + (cnt * ACE4_SZ)) +#define ACL4_GETENTRY(aclp, idx) \ + (zfsacl_entry_t)((char *)aclp + ACL4SZ_FROM_ACECNT(idx)) +#define ACLBUF_TO_ACES(aclp) ( + +#define zfsace4 zfsacl_entry +#define ACL4BUF_TO_ACES(aclp) ((struct zfsace4 *)(aclp + 2)) + +static boolean_t +acl_check_brand(zfsacl_t _acl, zfsacl_brand_t expected) +{ + if (_acl->brand != expected) { +#if ZFS_DEBUG + (void) fprintf(stderr, "Incorrect ACL brand"); +#endif + errno = ENOSYS; + return (B_FALSE); + } + return (B_TRUE); +} + +zfsacl_t +zfsacl_init(int _acecnt, zfsacl_brand_t _brand) +{ + size_t naclsz; + zfsacl_t out = NULL; + if (_brand != ZFSACL_BRAND_NFSV4) { + errno = EINVAL; + return (NULL); + } + + out = calloc(1, sizeof (struct zfsacl)); + if (out == NULL) { + return (NULL); + } + + naclsz = ACL4SZ_FROM_ACECNT(_acecnt); + out->aclbuf = calloc(naclsz, sizeof (char)); + if (out->aclbuf == NULL) { + free(out); + return (NULL); + } + out->brand = _brand; + out->aclbuf_size = naclsz; + return (out); +} + +void +zfsacl_free(zfsacl_t *_pacl) +{ + zfsacl_t to_free = *_pacl; + free(to_free->aclbuf); + free(to_free); + *_pacl = NULL; +} + +boolean_t +zfsacl_get_brand(zfsacl_t _acl, zfsacl_brand_t *_brandp) +{ + *_brandp = _acl->brand; + return (B_TRUE); +} + +boolean_t +zfsacl_get_aclflags(zfsacl_t _acl, zfsacl_aclflags_t *_paclflags) +{ + zfsacl_aclflags_t flags; + + if (!acl_check_brand(_acl, ZFSACL_BRAND_NFSV4)) { + return (B_FALSE); + } + + flags = ntohl(*ACL4_GET_FL(_acl->aclbuf)); + *_paclflags = flags; + return (B_TRUE); +} + +boolean_t +zfsacl_set_aclflags(zfsacl_t _acl, zfsacl_aclflags_t _aclflags) +{ + zfsacl_aclflags_t *flags; + + if (!acl_check_brand(_acl, ZFSACL_BRAND_NFSV4)) { + return (B_FALSE); + } + + if (ZFSACL_FLAGS_INVALID(_aclflags)) { +#if ZFS_DEBUG + (void) fprintf(stderr, "Incorrect ACL brand"); +#endif + errno = EINVAL; + return (B_FALSE); + } + + flags = ACL4_GET_FL(_acl->aclbuf); + *flags = htonl(_aclflags); + + return (B_TRUE); +} + +boolean_t +zfsacl_get_acecnt(zfsacl_t _acl, uint_t *pcnt) +{ + uint_t acecnt; + if (!acl_check_brand(_acl, ZFSACL_BRAND_NFSV4)) { + return (B_FALSE); + } + + acecnt = ntohl(*ACL4_GET_CNT(_acl->aclbuf)); + *pcnt = acecnt; + return (B_TRUE); +} + + +static boolean_t +validate_entry_idx(zfsacl_t _acl, int _idx) +{ + uint_t acecnt; + boolean_t ok; + + ok = zfsacl_get_acecnt(_acl, &acecnt); + if (!ok) { + return (B_FALSE); + } + + if ((((uint_t)_idx) + 1) > acecnt) { + errno = E2BIG; + return (B_FALSE); + } + + return (B_TRUE); +} + +/* out will be set to new required size if realloc required */ +static boolean_t +acl_get_new_size(zfsacl_t _acl, uint_t new_count, size_t *out) +{ + size_t current_sz, required_sz; + + if (new_count > ACL4_MAX_ENTRIES) { + errno = E2BIG; + return (B_FALSE); + } + current_sz = _acl->aclbuf_size; + required_sz = ACL4SZ_FROM_ACECNT(new_count); + + if (current_sz >= required_sz) { + *out = 0; + } else { + *out = required_sz; + } + + return (B_TRUE); +} + +boolean_t +zfsacl_create_aclentry(zfsacl_t _acl, int _idx, zfsacl_entry_t *_pentry) +{ + uint_t acecnt; + uint_t *pacecnt; + zfsacl_entry_t entry; + size_t new_size, new_offset, acl_size; + boolean_t ok; + struct zfsace4 *z = ACL4BUF_TO_ACES(_acl->aclbuf); + + ok = zfsacl_get_acecnt(_acl, &acecnt); + if (!ok) { + return (B_FALSE); + } + + if ((_idx != ZFSACL_APPEND_ENTRY) && (((uint_t)_idx) + 1 > acecnt)) { + errno = ERANGE; + return (B_FALSE); + } + + ok = acl_get_new_size(_acl, acecnt + 1, &new_size); + if (!ok) { + return (B_FALSE); + } + + acl_size = _acl->aclbuf_size; + + if (new_size != 0) { + zfsacl_t _tmp = realloc(_acl->aclbuf, new_size); + if (_tmp == NULL) { + errno = ENOMEM; + return (B_FALSE); + } + _acl->aclbuf = (uint_t *)_tmp; + _acl->aclbuf_size = new_size; + assert(new_size == (acl_size + ACE4_SZ)); + memset(_acl->aclbuf + (new_size - ACE4_SZ), 0, ACE4_SZ); + } + + if (_idx == ZFSACL_APPEND_ENTRY) { + *_pentry = &z[acecnt]; + goto done; + } + + new_offset = ACL4SZ_FROM_ACECNT(_idx); + + /* + * shift back one ace from offset + * to make room for new entry + */ + entry = &z[_idx]; + memmove(entry + 1, entry, acl_size - new_offset - ACE4_SZ); + + /* zero-out new ACE */ + memset(entry, 0, ACE4_SZ); + *_pentry = entry; + +done: + pacecnt = ACL4_GET_CNT(_acl->aclbuf); + *pacecnt = htonl(acecnt + 1); + return (B_TRUE); +} + +#if ZFS_DEBUG +static void +dump_entry(struct zfsace4 *z) +{ + fprintf(stderr, + "0x%08X %p " + "0x%08X %p " + "0x%08X %p " + "0x%08X %p " + "0x%08X %p \n", + z->netlong[0], + &z->netlong[0], + z->netlong[1], + &z->netlong[1], + z->netlong[2], + &z->netlong[2], + z->netlong[3], + &z->netlong[3], + z->netlong[4], + &z->netlong[4]); +} +#endif + +boolean_t +zfsacl_get_aclentry(zfsacl_t _acl, int _idx, zfsacl_entry_t *_pentry) +{ + zfsacl_entry_t entry; + + if (!validate_entry_idx(_acl, _idx)) { + return (B_FALSE); + } + + entry = ACL4_GETENTRY(_acl->aclbuf, _idx); + *_pentry = entry; +#if ZFS_DEBUG + dump_entry(entry); +#endif + return (B_TRUE); +} + +boolean_t +zfsacl_delete_aclentry(zfsacl_t _acl, int _idx) +{ + uint_t acecnt; + uint_t *aclacecnt = NULL; + boolean_t ok; + struct zfsace4 *z = ACL4BUF_TO_ACES(_acl->aclbuf); + size_t orig_sz, after_offset; + + if (!validate_entry_idx(_acl, _idx)) { + return (B_FALSE); + } + + ok = zfsacl_get_acecnt(_acl, &acecnt); + if (!ok) { + return (B_FALSE); + } + + if (acecnt == 1) { + /* ACL without entries is not permitted */ + errno = ERANGE; + return (B_FALSE); + } + + if (((uint_t)_idx) + 1 == acecnt) { + memset(&z[_idx], 0, ACE4_SZ); + } else { + orig_sz = _acl->aclbuf_size; + after_offset = orig_sz - ACL4SZ_FROM_ACECNT(_idx) - ACE4_SZ; + memmove(&z[_idx], &z[_idx + 1], after_offset); + } + + aclacecnt = ACL4_GET_CNT(_acl->aclbuf); + *aclacecnt = htonl(acecnt -1); + return (B_TRUE); +} + +#define ZFSACE_TYPE_OFFSET 0 +#define ZFSACE_FLAGSET_OFFSET 1 +#define ZFSACE_WHOTYPE_OFFSET 2 +#define ZFSACE_PERMSET_OFFSET 3 +#define ZFSACE_WHOID_OFFSET 4 +#define ZFSACE_SPECIAL_ID 0x00000001 +#define HAS_SPECIAL_ID(who) ((who == ZFSACE_SPECIAL_ID) ? B_TRUE : B_FALSE) + +boolean_t +zfsace_get_permset(zfsacl_entry_t _entry, zfsace_permset_t *_pperm) +{ + uint_t *entry = (uint_t *)_entry; + zfsace_permset_t perm; + + perm = ntohl(*(entry + ZFSACE_PERMSET_OFFSET)); + *_pperm = perm; + return (B_TRUE); +} + +boolean_t +zfsace_get_flagset(zfsacl_entry_t _entry, zfsace_flagset_t *_pflags) +{ + uint_t *entry = (uint_t *)_entry; + zfsace_flagset_t flags; + + flags = ntohl(*(entry + ZFSACE_FLAGSET_OFFSET)); + *_pflags = flags; + return (B_TRUE); +} + +boolean_t +zfsace_get_who(zfsacl_entry_t _entry, zfsace_who_t *pwho, zfsace_id_t *_paeid) +{ + struct zfsace4 *entry = (struct zfsace4 *)_entry; + zfsace_who_t whotype; + zfsace_id_t whoid; + zfsace_flagset_t flags; + boolean_t is_special; + + is_special = + HAS_SPECIAL_ID(ntohl(entry->netlong[ZFSACE_WHOTYPE_OFFSET])); + + if (is_special) { + whotype = ntohl(entry->netlong[ZFSACE_WHOID_OFFSET]); + whoid = ZFSACL_UNDEFINED_ID; + } else { + flags = ntohl(entry->netlong[ZFSACE_FLAGSET_OFFSET]); + if (ZFSACE_IS_GROUP(flags)) { + whotype = ZFSACL_GROUP; + } else { + whotype = ZFSACL_USER; + } + whoid = ntohl(entry->netlong[ZFSACE_WHOID_OFFSET]); + } + + *pwho = whotype; + *_paeid = whoid; + return (B_TRUE); +} + +boolean_t +zfsace_get_entry_type(zfsacl_entry_t _entry, zfsace_entry_type_t *_tp) +{ + uint_t *entry = (uint_t *)_entry; + zfsace_entry_type_t entry_type; + + entry_type = ntohl(*(entry + ZFSACE_TYPE_OFFSET)); + *_tp = entry_type; + return (B_TRUE); +} + +boolean_t +zfsace_set_permset(zfsacl_entry_t _entry, zfsace_permset_t _perm) +{ + uint_t *pperm = (uint_t *)_entry + ZFSACE_PERMSET_OFFSET; + + if (ZFSACE_ACCESS_MASK_INVALID(_perm)) { + errno = EINVAL; + return (B_FALSE); + } + + *pperm = htonl(_perm); + return (B_TRUE); +} + +boolean_t +zfsace_set_flagset(zfsacl_entry_t _entry, zfsace_flagset_t _flags) +{ + uint_t *pflags = (uint_t *)_entry + ZFSACE_FLAGSET_OFFSET; + + if (ZFSACE_FLAG_INVALID(_flags)) { + errno = EINVAL; + return (B_FALSE); + } + + *pflags = htonl(_flags); + return (B_TRUE); +} + +boolean_t +zfsace_set_who(zfsacl_entry_t _entry, zfsace_who_t _whotype, zfsace_id_t _whoid) +{ + struct zfsace4 *entry = (struct zfsace4 *)_entry; + uint_t *pspecial = &entry->netlong[ZFSACE_WHOTYPE_OFFSET]; + uint_t *pwhoid = &entry->netlong[ZFSACE_WHOID_OFFSET]; + uint_t special_flag, whoid; + zfsace_flagset_t flags; + + flags = ntohl(entry->netlong[ZFSACE_FLAGSET_OFFSET]); + + switch (_whotype) { + case ZFSACL_USER_OBJ: + case ZFSACL_EVERYONE: + whoid = _whotype; + special_flag = ZFSACE_SPECIAL_ID; + if (ZFSACE_IS_GROUP(flags)) { + zfsace_set_flagset(_entry, + flags & ~ZFSACE_IDENTIFIER_GROUP); + } + break; + case ZFSACL_GROUP_OBJ: + whoid = _whotype; + special_flag = ZFSACE_SPECIAL_ID; + if (!ZFSACE_IS_GROUP(flags)) { + zfsace_set_flagset(_entry, + flags | ZFSACE_IDENTIFIER_GROUP); + } + break; + case ZFSACL_USER: + if (_whoid == ZFSACL_UNDEFINED_ID) { + errno = EINVAL; + return (B_FALSE); + } + whoid = _whoid; + special_flag = 0; + if (ZFSACE_IS_GROUP(flags)) { + zfsace_set_flagset(_entry, + flags & ~ZFSACE_IDENTIFIER_GROUP); + } + break; + case ZFSACL_GROUP: + if (_whoid == ZFSACL_UNDEFINED_ID) { + errno = EINVAL; + return (B_FALSE); + } + whoid = _whoid; + special_flag = 0; + if (!ZFSACE_IS_GROUP(flags)) { + zfsace_set_flagset(_entry, + flags | ZFSACE_IDENTIFIER_GROUP); + } + break; + default: + errno = EINVAL; + return (B_FALSE); + } + + *pspecial = htonl(special_flag); + *pwhoid = htonl(whoid); + return (B_TRUE); +} + +boolean_t +zfsace_set_entry_type(zfsacl_entry_t _entry, zfsace_entry_type_t _tp) +{ + uint_t *ptype = (uint_t *)_entry + ZFSACE_TYPE_OFFSET; + + if (ZFSACE_TYPE_INVALID(_tp)) { + errno = EINVAL; + return (B_FALSE); + } + + *ptype = htonl(_tp); + return (B_TRUE); +} + +#if ZFS_DEBUG +static void +dump_xattr(uint_t *buf, size_t len) +{ + size_t i; + + fprintf(stderr, "off: 0, 0x%08x, ptr: %p | ", ntohl(buf[0]), &buf[0]); + fprintf(stderr, "off: 1, 0x%08x, ptr: %p | ", ntohl(buf[1]), &buf[0]); + + for (i = 2; i < (len / sizeof (uint_t)); i++) { + if (((i -2) % 5) == 0) { + fprintf(stderr, "\n"); + } + fprintf(stderr, "off: %ld, 0x%08x, ptr: %p\n", + i, ntohl(buf[i]), &buf[i]); + } +} +#endif + +zfsacl_t +zfsacl_get_fd(int fd, zfsacl_brand_t _brand) +{ + zfsacl_t out = NULL; + ssize_t res; + + if (_brand != ZFSACL_BRAND_NFSV4) { + errno = EINVAL; + return (NULL); + } + + out = zfsacl_init(ACL4_MAX_ENTRIES, _brand); + if (out == NULL) { + return (NULL); + } + + res = fgetxattr(fd, ACL4_XATTR, out->aclbuf, out->aclbuf_size); + if (res == -1) { + zfsacl_free(&out); + return (NULL); + } +#if ZFS_DEBUG + dump_xattr(out->aclbuf, out->aclbuf_size); +#endif + + return (out); +} + +zfsacl_t +zfsacl_get_file(const char *_path_p, zfsacl_brand_t _brand) +{ + zfsacl_t out = NULL; + ssize_t res; + + if (_brand != ZFSACL_BRAND_NFSV4) { + errno = EINVAL; + return (NULL); + } + + out = zfsacl_init(ACL4_MAX_ENTRIES, _brand); + if (out == NULL) { + return (NULL); + } + + res = getxattr(_path_p, ACL4_XATTR, out->aclbuf, out->aclbuf_size); + if (res == -1) { + zfsacl_free(&out); + return (NULL); + } +#if ZFS_DEBUG + dump_xattr(out->aclbuf, out->aclbuf_size); +#endif + + return (out); +} + +zfsacl_t +zfsacl_get_link(const char *_path_p, zfsacl_brand_t _brand) +{ + zfsacl_t out = NULL; + ssize_t res; + + if (_brand != ZFSACL_BRAND_NFSV4) { + errno = EINVAL; + return (NULL); + } + + out = zfsacl_init(ACL4_MAX_ENTRIES, _brand); + if (out == NULL) { + return (NULL); + } + + res = lgetxattr(_path_p, ACL4_XATTR, out->aclbuf, out->aclbuf_size); + if (res == -1) { + zfsacl_free(&out); + return (NULL); + } + +#if ZFS_DEBUG + dump_xattr(out->aclbuf, out->aclbuf_size); +#endif + return (out); +} + +static boolean_t +xatbuf_from_acl(zfsacl_t acl, char **pbuf, size_t *bufsz) +{ + uint_t acecnt; + size_t calculated_acl_sz; + boolean_t ok; + + ok = zfsacl_get_acecnt(acl, &acecnt); + if (!ok) { + return (B_FALSE); + } + + if (acecnt == 0) { + errno = ENODATA; + } else if (acecnt > ACL4_MAX_ENTRIES) { + errno = ERANGE; + return (B_FALSE); + } + + calculated_acl_sz = ACL4SZ_FROM_ACECNT(acecnt); + assert(calculated_acl_sz <= acl->aclbuf_size); + + *pbuf = (char *)acl->aclbuf; + + *bufsz = calculated_acl_sz; + return (B_TRUE); +} + +boolean_t +zfsacl_set_fd(int _fd, zfsacl_t _acl) +{ + int err; + boolean_t ok; + char *buf = NULL; + size_t bufsz = 0; + + ok = xatbuf_from_acl(_acl, &buf, &bufsz); + if (!ok) { + return (B_FALSE); + } + + err = fsetxattr(_fd, ACL4_XATTR, buf, bufsz, 0); + if (err) { + return (B_FALSE); + } + return (B_TRUE); +} + +boolean_t +zfsacl_set_file(const char *_path_p, zfsacl_t _acl) +{ + int err; + boolean_t ok; + char *buf = NULL; + size_t bufsz = 0; + + ok = xatbuf_from_acl(_acl, &buf, &bufsz); + if (!ok) { + return (B_FALSE); + } + + err = setxattr(_path_p, ACL4_XATTR, buf, bufsz, 0); + if (err) { + return (B_FALSE); + } + return (B_TRUE); +} + +boolean_t +zfsacl_set_link(const char *_path_p, zfsacl_t _acl) +{ + int err; + boolean_t ok; + char *buf = NULL; + size_t bufsz = 0; + + ok = xatbuf_from_acl(_acl, &buf, &bufsz); + if (!ok) { + return (B_FALSE); + } + + err = lsetxattr(_path_p, ACL4_XATTR, buf, bufsz, 0); + if (err) { + return (B_FALSE); + } + return (B_TRUE); +} + +boolean_t +zfsacl_to_native(zfsacl_t _acl, struct native_acl *pnative) +{ + char *to_copy = NULL; + char *out_buf = NULL; + size_t bufsz; + boolean_t ok; + + if (pnative == NULL) { + errno = ENOMEM; + return (B_FALSE); + } + + ok = xatbuf_from_acl(_acl, &to_copy, &bufsz); + if (!ok) { + return (B_FALSE); + } + + out_buf = calloc(bufsz, sizeof (char)); + if (out_buf == NULL) { + errno = ENOMEM; + return (B_FALSE); + } + memcpy(out_buf, to_copy, bufsz); + pnative->data = out_buf; + pnative->datalen = bufsz; + pnative->brand = _acl->brand; + return (B_TRUE); +} + +boolean_t +zfsacl_is_trivial(zfsacl_t _acl, boolean_t *trivialp) +{ + (void) _acl; + (void) trivialp; + errno = EOPNOTSUPP; + return (B_FALSE); +} + +#define MAX_ENTRY_LENGTH 512 + +static boolean_t +format_perms(char *str, const zfsacl_entry_t entry, size_t *off) +{ + int i, cnt = 0; + zfsace_permset_t p; + + if (!zfsace_get_permset(entry, &p)) { + return (B_FALSE); + } + + for (i = 0; i < ARRAY_SIZE(aceperm2name); i++) { + char to_set; + + if (aceperm2name[i].letter == '\0') { + continue; + } + if (p & aceperm2name[i].perm) { + to_set = aceperm2name[i].letter; + } else { + to_set = '-'; + } + str[cnt] = to_set; + cnt++; + } + + *off += cnt; + return (B_TRUE); +} + +static boolean_t +format_flags(char *str, const zfsacl_entry_t entry, size_t *off) +{ + int i, cnt = 0; + zfsace_flagset_t flag; + + if (!zfsace_get_flagset(entry, &flag)) { + return (B_FALSE); + } + + for (i = 0; i < ARRAY_SIZE(aceflag2name); i++) { + char to_set; + + if (aceflag2name[i].letter == '\0') { + continue; + } + if (flag & aceflag2name[i].flag) { + to_set = aceflag2name[i].letter; + } else { + to_set = '-'; + } + str[cnt] = to_set; + cnt++; + } + + *off += cnt; + return (B_TRUE); +} + +static boolean_t +format_who(char *str, size_t sz, const zfsacl_entry_t _entry, size_t *off) +{ + uid_t id; + zfsace_who_t who; + int cnt = 0; + + if (!zfsace_get_who(_entry, &who, &id)) { + return (B_FALSE); + } + + switch (who) { + case ZFSACL_USER_OBJ: + cnt = snprintf(str, sz, "owner@"); + break; + case ZFSACL_GROUP_OBJ: + cnt = snprintf(str, sz, "group@"); + break; + case ZFSACL_EVERYONE: + cnt = snprintf(str, sz, "everyone@"); + break; + case ZFSACL_USER: + cnt = snprintf(str, sz, "user:%d", id); + break; + case ZFSACL_GROUP: + cnt = snprintf(str, sz, "group:%d", id); + break; + default: + errno = EINVAL; + return (B_FALSE); + } + + if (cnt == -1) { + return (B_FALSE); + } + + *off += cnt; + return (B_TRUE); +} + +static boolean_t +format_entry_type(char *str, size_t sz, const zfsacl_entry_t _entry, + size_t *off) +{ + zfsace_entry_type_t entry_type; + int cnt = 0; + + if (!zfsace_get_entry_type(_entry, &entry_type)) { + return (B_FALSE); + } + + switch (entry_type) { + case ZFSACL_ENTRY_TYPE_ALLOW: + cnt = snprintf(str, sz, "allow"); + break; + case ZFSACL_ENTRY_TYPE_DENY: + cnt = snprintf(str, sz, "deny"); + break; + case ZFSACL_ENTRY_TYPE_AUDIT: + cnt = snprintf(str, sz, "audit"); + break; + case ZFSACL_ENTRY_TYPE_ALARM: + cnt = snprintf(str, sz, "alarm"); + break; + default: + errno = EINVAL; + return (B_FALSE); + } + + if (cnt == -1) { + return (B_FALSE); + } + + *off += cnt; + return (B_TRUE); +} + +static boolean_t +add_format_separator(char *str, size_t sz, size_t *off) +{ + int cnt; + + cnt = snprintf(str, sz, ":"); + if (cnt == -1) + return (B_FALSE); + + *off += cnt; + return (B_TRUE); +} + +static size_t +format_entry(char *str, size_t sz, const zfsacl_entry_t _entry) +{ + size_t off = 0; + size_t slen = 0; + size_t tocopy = 0; + char buf[MAX_ENTRY_LENGTH + 1] = { 0 }; + + if (!format_who(buf, sizeof (buf), _entry, &off)) + return (-1); + + if (!add_format_separator(buf +off, sizeof (buf) - off, &off)) + return (-1); + + if (!format_perms(buf + off, _entry, &off)) + return (-1); + + if (!add_format_separator(buf +off, sizeof (buf) - off, &off)) + return (-1); + + if (!format_flags(buf + off, _entry, &off)) + return (-1); + + if (!add_format_separator(buf +off, sizeof (buf) - off, &off)) + return (-1); + + if (!format_entry_type(buf + off, sizeof (buf) - off, _entry, &off)) + return (-1); + + buf[off] = '\n'; + slen = strlen(buf); + if (slen >= sz) + tocopy = sz - 1; + else + tocopy = slen; + memcpy(str, buf, tocopy); + str[tocopy] = '\0'; + return (tocopy); +} + +char * +zfsacl_to_text(zfsacl_t _acl) +{ + uint_t acecnt, i; + char *str = NULL; + size_t off = 0, bufsz; + + if (!zfsacl_get_acecnt(_acl, &acecnt)) { + return (NULL); + } + + str = calloc(acecnt, MAX_ENTRY_LENGTH); + if (str == NULL) { + return (NULL); + } + + bufsz = acecnt * MAX_ENTRY_LENGTH; + + for (i = 0; i < acecnt; i++) { + zfsacl_entry_t entry; + size_t written; + + if (!zfsacl_get_aclentry(_acl, i, &entry)) { + free(str); + return (NULL); + } + + written = format_entry(str + off, bufsz - off, entry); + if (written == (size_t)-1) { + free(str); + return (NULL); + } + + off += written; + } + + return (str); +} diff --git a/rpm/generic/zfs.spec.in b/rpm/generic/zfs.spec.in index d0d850af2629..461f6ad84c7f 100644 --- a/rpm/generic/zfs.spec.in +++ b/rpm/generic/zfs.spec.in @@ -229,6 +229,20 @@ This package provides support for managing ZFS filesystems %postun -n libzfs6 -p /sbin/ldconfig %endif +%package -n libzfsacl1 +Summary: Native ZFS filesystem library for accessing NFS v4.1 ACLs +Group: System Environment/Kernel + +%description -n libzfsacl1 +This package provides support for accessing native NFS v4.1 style ZFS ACLs + +%if %{defined ldconfig_scriptlets} +%ldconfig_scriptlets -n libzfsacl1 +%else +%post -n libzfsacl1 -p /sbin/ldconfig +%postun -n libzfsacl1 -p /sbin/ldconfig +%endif + %package -n libzfs6-devel Summary: Development headers Group: System Environment/Kernel @@ -349,6 +363,22 @@ This package contains the pam_zfs_key PAM module, which provides support for unlocking datasets on user login. %endif +%package -n python%{__python_pkg_version}-libzfsacl +Summary: Python bindings for libzfsacl1 +Group: System Environment/Kernel +Requires: python%{__python_pkg_version} + +%if 0%{?rhel}%{?centos}%{?fedora}%{?suse_version}%{?openEuler} +%if 0%{?centos} == 7 +BuildRequires: python36-setuptools +%else +BuildRequires: python%{__python_pkg_version}-setuptools +%endif +%endif + +%description -n python%{__python_pkg_version}-libzfsacl +This package contains python bindings for libzfsacl1. + %prep %if %{with debug} %define debug --enable-debug @@ -547,7 +577,13 @@ systemctl --system daemon-reload >/dev/null || true %{_libdir}/libuutil.so.* %files -n libzfs6 -%{_libdir}/libzfs*.so.* +%{_libdir}/libzfs.so.* +%{_libdir}/libzfsbootenv.so.* +%{_libdir}/libzfs_core.so.* + +%files -n libzfsacl1 +%{_libdir}/libzfsacl.so.* +%{_libdir}/libsunacl.so.* %files -n libzfs6-devel %{_pkgconfigdir}/libzfs.pc @@ -591,3 +627,7 @@ systemctl --system daemon-reload >/dev/null || true %{_libdir}/security/* %{_datadir}/pam-configs/* %endif + +%files -n python%{__python_pkg_version}-libzfsacl +%{__python_sitelib}/libzfsacl-*/* +%{__python_sitelib}/libzfsacl.cpython*.so From a59ea0a24e33ad953f3887d3df0914a23a5545c4 Mon Sep 17 00:00:00 2001 From: Umer Saleem Date: Thu, 12 Oct 2023 10:16:53 +0500 Subject: [PATCH 4/5] Add NFSv4 ACL get/set scripts This commit adds zfs_getnfs4facl and zfs_setnfs4facl. zfs_getnfs4facl will display the NFSv4 ACLs for a file or directory on a ZFS filesystem with acltype set to nfsv4 that exposes NFSv4 ACLs as a system.nfs4_acl_xdr xattr. zfs_setnfs4facl manipulates the NFSv4 ACLs of one or more files or directories, on a ZFS filesystem with acltype set to nfsv4. Both scripts provide output compatible with getfacl and setfacl on FreeBSD, and provides support for viewing and managing ACL features present in the NFSv4.1. Signed-off-by: Umer Saleem --- cmd/Makefile.am | 11 +- cmd/zfs_getnfs4facl.in | 314 ++++++++ cmd/zfs_setnfs4facl.in | 908 ++++++++++++++++++++++++ contrib/debian/openzfs-zfsutils.install | 2 + rpm/generic/zfs.spec.in | 5 +- 5 files changed, 1236 insertions(+), 4 deletions(-) create mode 100644 cmd/zfs_getnfs4facl.in create mode 100644 cmd/zfs_setnfs4facl.in diff --git a/cmd/Makefile.am b/cmd/Makefile.am index 96040976e53e..0387b744c6ef 100644 --- a/cmd/Makefile.am +++ b/cmd/Makefile.am @@ -98,13 +98,18 @@ endif if USING_PYTHON -bin_SCRIPTS += arc_summary arcstat dbufstat zilstat -CLEANFILES += arc_summary arcstat dbufstat zilstat -dist_noinst_DATA += %D%/arc_summary %D%/arcstat.in %D%/dbufstat.in %D%/zilstat.in +bin_SCRIPTS += arc_summary arcstat dbufstat zilstat \ + zfs_getnfs4facl zfs_setnfs4facl +CLEANFILES += arc_summary arcstat dbufstat zilstat \ + zfs_getnfs4facl zfs_setnfs4facl +dist_noinst_DATA += %D%/arc_summary %D%/arcstat.in %D%/dbufstat.in %D%/zilstat.in \ + %D%/zfs_getnfs4facl.in %D%/zfs_setnfs4facl.in $(call SUBST,arcstat,%D%/) $(call SUBST,dbufstat,%D%/) $(call SUBST,zilstat,%D%/) +$(call SUBST,zfs_getnfs4facl,%D%/) +$(call SUBST,zfs_setnfs4facl,%D%/) arc_summary: %D%/arc_summary $(AM_V_at)cp $< $@ endif diff --git a/cmd/zfs_getnfs4facl.in b/cmd/zfs_getnfs4facl.in new file mode 100644 index 000000000000..59f44b4a0eec --- /dev/null +++ b/cmd/zfs_getnfs4facl.in @@ -0,0 +1,314 @@ +#!/usr/bin/env @PYTHON_SHEBANG@ +# +# This script will display the NFSv4 ACLs for a file or directory on a +# ZFS filesystem with acltype set to nfsv4 that exposes NFSv4 ACLs as a +# system.nfs4_acl_xdr xattr. +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License, Version 1.0 only +# (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 https://opensource.org/licenses/CDDL-1.0. +# 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] +# +# This script must remain compatible with Python 3.6+. +# + +# +# Copyright (c) 2023 by iXsystems, Inc. All rights reserved. +# + +import sys +import os +import grp +import pwd +import argparse +import json +import libzfsacl + +SUCCESSFUL_ACCESS_ACE_FLAG = 0x10 +FAILED_ACCESS_ACE_FLAG = 0x20 +ACE_IDENTIFIER_GROUP = 0x40 + +def parse_args(): + info = \ +"""An NFSv4 ACL consists of one or more NFSv4 ACEs, each delimited by commas or whitespace. +An NFSv4 ACE is written as a colon-delimited string in one of the following formats:\n + :::: + :::\n + * - named user or group, or one of: \"owner@\", \"group@\", \"everyone@\" + in case of named users or groups, principal must be preceded with one of the following: + 'user:' or 'u:' + 'group:' or 'g:'\n + note: numerical user or group IDs may be specified in lieu of user or group name.\n + * - one or more of: + 'r' read-data / list-directory + 'w' write-data / create-file + 'p' append-data / create-subdirectory + 'x' execute + 'd' delete + 'D' delete-child + 'a' read-attrs + 'A' write-attrs + 'R' read-named-attrs + 'W' write-named-attrs + 'c' read-ACL + 'C' write-ACL + 'o' write-owner + 's' synchronize\n + * - zero or more (depending on ) of: + 'f' file-inherit + 'd' directory-inherit + 'n' no-propagate-inherit + 'i' inherit-only + 'I' inherited\n + * - one of: + 'allow' allow + 'deny' deny""" + parser = argparse.ArgumentParser( + description='Get NFSv4 file/directory access control lists', + add_help=True, formatter_class=argparse.RawTextHelpFormatter, + epilog=info) + + parser.add_argument('-i', '--append-id', action='store_true', + help='append numerical ids to end of entries containing user or group name') + parser.add_argument('-j', '--json', action='store_true', + help='output ACL in JSON format') + parser.add_argument('-n', '--numeric', action='store_true', + help='display user and group IDs rather than user or group name') + parser.add_argument('-v', '--verbose', action='store_true', + help='display access mask and flags in a verbose form') + parser.add_argument('-q', '--quiet', action='store_true', + help='do not write commented information about file name and ownership') + parser.add_argument('file', nargs='+', type=str, + help='File(s) to process') + + return parser.parse_args() + +def validate_filepath(files): + for x in files: + if not os.path.exists(x): + print(sys.argv[0] + ': File not found: ' + x, file=sys.stderr) + sys.exit(1) + +def stat(file): + st = os.stat(file) + print('# File: ' + file) + print('# owner: ' + str(st.st_uid)) + print('# group: ' + str(st.st_gid)) + print('# mode: ' + str(oct(st.st_mode))) + +def nfs4_acl_is_trivial(acl_flags): + trivial = (acl_flags & libzfsacl.ACL_IS_TRIVIAL) != 0 + print('# trivial_acl: ' + str(trivial)) + +def nfs4_acl_flags(acl_flags, to_json): + nfs4_acl_str = { + libzfsacl.ACL_AUTO_INHERIT : ('autoinherit', ''), + libzfsacl.ACL_DEFAULT : ('defaulted', ''), + libzfsacl.ACL_PROTECTED : ('protected', '') + } + if to_json: + return format_to_json(acl_flags, nfs4_acl_str) + else: + flags = "" + for x in nfs4_acl_str: + if acl_flags & x != 0: + flags += nfs4_acl_str[x][0] + ',' + if not flags: + flags = 'none' + else: + flags = flags[:-1] + ':' + print('# ACL flags: ' + flags) + +def format_who(who, numeric, to_json): + who_strs = { + libzfsacl.WHOTYPE_UNDEFINED : '', + libzfsacl.WHOTYPE_USER_OBJ : 'owner@', + libzfsacl.WHOTYPE_GROUP_OBJ : 'group@', + libzfsacl.WHOTYPE_EVERYONE : 'everyone@', + libzfsacl.WHOTYPE_USER : 'user', + libzfsacl.WHOTYPE_GROUP : 'group' + } + + if who[0] == libzfsacl.WHOTYPE_GROUP: + name = grp.getgrgid(who[1])[0] + elif who[0] == libzfsacl.WHOTYPE_USER: + name = pwd.getpwuid(who[1])[0] + + if who[0] == libzfsacl.WHOTYPE_GROUP or who[0] == libzfsacl.WHOTYPE_USER: + if not to_json and not numeric: + return who_strs[who[0]] + ':' + name + elif not to_json and numeric: + return who_strs[who[0]] + ':' + str(who[1]) + elif to_json: + return { + 'tag' : who_strs[who[0]], + 'name' : name, + 'id' : who[1] + } + elif who[0] <= libzfsacl.WHOTYPE_EVERYONE: + if not to_json: + return who_strs[who[0]] + else: + return { + 'tag' : who_strs[who[0]], + 'id' : -1 + } + +def format_id(who): + if who[0] == libzfsacl.WHOTYPE_GROUP or who[0] == libzfsacl.WHOTYPE_USER: + return str(who[1]) + else: + return None + +def format_to_text(field, to_text, verbose): + text = '' + if verbose: + seperator = '/' + selector = 0 + skip = '' + else: + seperator = '' + selector = 1 + skip = '-' + for x in to_text: + if field & x != 0: + text += (to_text[x][selector] + seperator) + else: + text += skip + if verbose: + text = text[:-1] + return text + +def format_to_json(field, to_text): + data = {} + selector = 0 + for x in to_text: + if field & x != 0: + data[to_text[x][selector].upper()] = True + else: + data[to_text[x][selector].upper()] = False + return data + +def format_perms(permset, verbose, to_json): + perms_to_text = { + libzfsacl.PERM_READ_DATA : ('read_data', 'r'), + libzfsacl.PERM_WRITE_DATA : ('write_data', 'w'), + libzfsacl.PERM_EXECUTE : ('execute', 'x'), + libzfsacl.PERM_APPEND_DATA : ('append_data', 'p'), + libzfsacl.PERM_DELETE_CHILD : ('delete_child', 'D'), + libzfsacl.PERM_DELETE : ('delete', 'd'), + libzfsacl.PERM_READ_ATTRIBUTES : ('read_attributes', 'a'), + libzfsacl.PERM_WRITE_ATTRIBUTES : ('write_attributes', 'A'), + libzfsacl.PERM_READ_NAMED_ATTRS : ('read_named_attrs', 'R'), + libzfsacl.PERM_WRITE_NAMED_ATTRS : ('write_named_attrs', 'W'), + libzfsacl.PERM_READ_ACL : ('read_acl', 'c'), + libzfsacl.PERM_WRITE_ACL : ('write_acl', 'C'), + libzfsacl.PERM_WRITE_OWNER : ('write_owner', 'o'), + libzfsacl.PERM_SYNCHRONIZE : ('synchronize', 's') + } + if to_json: + return format_to_json(permset, perms_to_text) + else: + return format_to_text(permset, perms_to_text, verbose) + +def format_flagset(flagset, verbose, to_json): + flags_to_text = { + libzfsacl.FLAG_FILE_INHERIT : ('file_inherit', 'f'), + libzfsacl.FLAG_DIRECTORY_INHERIT : ('dir_inherit', 'd'), + libzfsacl.FLAG_INHERIT_ONLY : ('inherit_only', 'i'), + libzfsacl.FLAG_NO_PROPAGATE_INHERIT : ('no_propagate', 'n'), + SUCCESSFUL_ACCESS_ACE_FLAG : ('successful_access', 'S'), + FAILED_ACCESS_ACE_FLAG : ('failed_access', 'F'), + libzfsacl.FLAG_INHERITED : ('inherited', 'I'), + } + if to_json: + if flagset == 0 or flagset == ACE_IDENTIFIER_GROUP: + return {"BASIC" : "NOINHERIT"} + return format_to_json(flagset, flags_to_text) + else: + return format_to_text(flagset, flags_to_text, verbose) + +def format_type(etype): + if etype == libzfsacl.ENTRY_TYPE_ALLOW: + return 'allow' + elif etype == libzfsacl.ENTRY_TYPE_DENY: + return 'deny' + +def format_entry(entry, flags): + return { + 'who' : format_who(entry.who, flags['numeric'], flags['to_json']), + 'permset' : format_perms(entry.permset, flags['verbose'], flags['to_json']), + 'flagset' : format_flagset(entry.flagset, flags['verbose'], flags['to_json']), + 'type' : format_type(entry.entry_type), + 'id' : format_id(entry.who) + } + +def print_acl_text(acl, numeric, verbose, append_id): + flags = { + 'numeric' : numeric, + 'verbose' : verbose, + 'append_id' : append_id, + 'to_json' : False + } + aces = [] + for i in range (acl.ace_count): + aces.append(format_entry(acl.get_entry(i), flags)) + for ace in aces: + if append_id and ace['id'] is not None: + print(f"{ace['who']:>18}:{ace['permset']}:{ace['flagset']}:{ace['type']}:{ace['id']}") + else: + print(f"{ace['who']:>18}:{ace['permset']}:{ace['flagset']}:{ace['type']}") + +def print_acl_json(acl, path): + flags = { + 'numeric' : False, + 'verbose' : False, + 'append_id' : False, + 'to_json' : True + } + aces = [] + for i in range (acl.ace_count): + ace = format_entry(acl.get_entry(i), flags) + entry = ace.pop('who') + entry['perms'] = ace['permset'] + entry['flags'] = ace['flagset'] + entry['type'] = ace['type'].upper() + aces.append(entry) + data = {} + data['acl'] = aces + data['nfs41_flags'] = nfs4_acl_flags(acl.acl_flags, True) + data['trivial'] = (acl.acl_flags & libzfsacl.ACL_IS_TRIVIAL) != 0 + data['uid'] = os.stat(path).st_uid + data['gid'] = os.stat(path).st_gid + data['path'] = path + print(json.dumps(data)) + +def main(): + args = parse_args() + validate_filepath(args.file) + for x in args.file: + acl = libzfsacl.Acl(path=x) + if not args.quiet and not args.json: + stat(x) + nfs4_acl_is_trivial(acl.acl_flags) + nfs4_acl_flags(acl.acl_flags, False) + if args.json: + print_acl_json(acl, x) + else: + print_acl_text(acl, args.numeric, args.verbose, args.append_id) + +if __name__ == '__main__': + main() diff --git a/cmd/zfs_setnfs4facl.in b/cmd/zfs_setnfs4facl.in new file mode 100644 index 000000000000..9cea8769b547 --- /dev/null +++ b/cmd/zfs_setnfs4facl.in @@ -0,0 +1,908 @@ +#!/usr/bin/env @PYTHON_SHEBANG@ +# +# This script manipulates the NFSv4 ACLs for one or more files or +# directories on a ZFS filesystem with acltype set to nfsv4. +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License, Version 1.0 only +# (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 https://opensource.org/licenses/CDDL-1.0. +# 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] +# +# This script must remain compatible with Python 3.6+. +# + +# +# Copyright (c) 2023 by iXsystems, Inc. All rights reserved. +# + +import sys +import os +import grp +import pwd +import argparse +import json +import libzfsacl +from enum import Enum +import re +import stat +import tempfile +import subprocess + +class Action(Enum): + NO_ACTION = 0 + MODIFY = 1 + SUBSTITUTE = 2 + REMOVE = 3 + INSERT = 4 + EDIT = 5 + STRIP = 6 + SET_FLAGS = 7 + APPLY_JSON = 8 + +class WalkType(Enum): + DEFAULT = 0 # Follow symbolic link args, skip links in sub-dirs + LOGICAL = 1 # Follow all symbolic links + PHYSICAL = 2 # Skip all symbolic links + +class HelpFormatter(argparse.HelpFormatter): + def add_usage(self, usage, actions, groups, prefix=None): + pass + +SUCCESSFUL_ACCESS_ACE_FLAG = 0x10 +FAILED_ACCESS_ACE_FLAG = 0x20 + +NFS4_ACE_BASE_ALLOW_PSARC = libzfsacl.PERM_READ_ACL | \ + libzfsacl.PERM_READ_ATTRIBUTES | \ + libzfsacl.PERM_SYNCHRONIZE | \ + libzfsacl.PERM_READ_NAMED_ATTRS + +NFS4_ACE_USER_ALLOW_PSARC = libzfsacl.PERM_WRITE_ACL | \ + libzfsacl.PERM_WRITE_OWNER | \ + libzfsacl.PERM_WRITE_ATTRIBUTES | \ + libzfsacl.PERM_WRITE_NAMED_ATTRS + +NFS4_ACE_POSIX_WRITE = libzfsacl.PERM_WRITE_DATA | \ + libzfsacl.PERM_APPEND_DATA | \ + libzfsacl.PERM_DELETE_CHILD + +def usage(ret): + info = \ +""" - Manipulate NFSv4 file/directory access control lists +Usage: nfs4xdr_setfacl [OPTIONS] COMMAND file ... + .. where COMMAND is one of: + -a acl_spec[,index] add ACL entries in acl_spec at index (DEFAULT: 1) + -A file[,index] read ACL entries to add from file + -x acl_spec | index remove ACL entries or entry-at-index from ACL + -X file read ACL entries to remove from file + -s acl_spec set ACL to acl_spec (replaces existing ACL) + -S file read ACL entries to set from file + -b file strip ACL entry from the file + -j replace ACL with one represented in JSON + -p aclflags file set specified ACL flags on file + -e, --edit edit ACL in $EDITOR (DEFAULT: vi); save on clean exit + -m from_ace to_ace modify in-place: replace 'from_ace' with 'to_ace' + --version print version and exit + -?, -h, --help display this text and exit + + .. and where OPTIONS is any (or none) of: + -R, --recursive recursively apply to all files and directories + -L, --logical logical walk, follow symbolic links + -P, --physical physical walk, do not follow symbolic links + --test print resulting ACL, do not save changes +""" + print(sys.argv[0] + info, file=sys.stderr) + sys.exit(ret) + +def verify_optional_value(arg): + args = arg.split(',') + if len(args) == 0: + raise argparse.ArgumentTypeError('Atleast one value is required') + elif len(args) > 2: + raise argparse.ArgumentTypeError('Too many values') + elif len(args) == 2: + if args[1].isdecimal(): + args[1] = int(args[1]) + return args + else: + raise argparse.ArgumentTypeError('Integer index expected') + else: + return args + +def validate_action(act, action): + if act == Action.NO_ACTION: + return action + else: + print('More than one action specified', file=sys.stderr) + usage(1) + +def validate_walk_type(walk, walk_type, recursive): + if walk == WalkType.DEFAULT: + if recursive: + return walk_type + else: + print('Walk Type specified without recursive flag', + file=sys.stderr) + usage(1) + else: + print('More than one walk type specified', file=sys.stderr) + usage(1) + +def validate_filepath(f): + if not os.path.exists(f): + print(f'{sys.argv[0]}: File not found: {f}', file=sys.stderr) + sys.exit(1) + +def parse_args(): + parser = argparse.ArgumentParser( + description='Manipulate NFSv4 file/directory access control lists', + add_help=False, formatter_class=HelpFormatter) + + parser.add_argument('-a', '--add-spec', type=verify_optional_value) + parser.add_argument('-A', '--add-file', type=verify_optional_value) + parser.add_argument('-s', '--set-spec', type=str) + parser.add_argument('-S', '--set-file', type=str) + parser.add_argument('-x', '--remove-spec', type=str) + parser.add_argument('-X', '--remove-file', type=str) + parser.add_argument('-m', '--modify', nargs=2, type=str) + parser.add_argument('-p', '--set-flags', type=str) + parser.add_argument('-e', '--edit', action='store_true') + parser.add_argument('-b', '--strip', action='store_true') + parser.add_argument('-j', '--apply-json', type=str) + parser.add_argument('-t', '--test', action='store_true') + parser.add_argument('-R', '--recursive', action='store_true') + parser.add_argument('-P', '--physical', action='store_true') + parser.add_argument('-L', '--logical', action='store_true') + parser.add_argument('-h', '--help', action='store_true') + parser.add_argument('file', type=str) + + try: + args, unknown = parser.parse_known_args() + except argparse.ArgumentTypeError as e: + print(e, file=sys.stderr) + + if unknown: + usage(2) + if args.help: + usage(0) + + action = Action.NO_ACTION + walk = WalkType.DEFAULT + spec_file = False + obj = None + if args.add_spec != None: + action = validate_action(action, Action.INSERT) + obj = args.add_spec + if args.add_file != None: + action = validate_action(action, Action.INSERT) + obj = args.add_file + spec_file = True + if args.set_spec != None: + action = validate_action(action, Action.SUBSTITUTE) + obj = [args.set_spec] + if args.set_file != None: + action = validate_action(action, Action.SUBSTITUTE) + obj = [args.set_file] + spec_file = True + if args.remove_spec != None: + action = validate_action(action, Action.REMOVE) + obj = [args.remove_spec] + if args.remove_file != None: + action = validate_action(action, Action.REMOVE) + obj = [args.remove_file] + spec_file = True + if args.modify != None: + action = validate_action(action, Action.MODIFY) + obj = args.modify + if args.set_flags != None: + action = validate_action(action, Action.SET_FLAGS) + obj = [args.set_flags] + if args.edit == True: + action = validate_action(action, Action.EDIT) + if args.strip == True: + action = validate_action(action, Action.STRIP) + if args.apply_json != None: + action = validate_action(action, Action.APPLY_JSON) + obj = [args.apply_json] + + if args.physical: + walk = validate_walk_type(walk, WalkType.PHYSICAL, args.recursive) + if args.logical: + walk = validate_walk_type(walk, WalkType.LOGICAL, args.recursive) + + if action == Action.NO_ACTION: + print('No action specified') + sys.exit(1) + + data = { + 'action' : action, + 'specfile' : spec_file, + 'object' : obj, + 'recursive' : (args.recursive, walk), + 'test' : args.test, + 'file' : args.file + } + + return data + +def read_acl_spec_from_file(filepath): + validate_filepath(filepath) + with open(filepath, 'r') as f: + lines = f.readlines() + lines = [line for line in lines if not line.startswith('#')] + return ''.join(lines) + +def format_who(who): + who_strs = { + libzfsacl.WHOTYPE_UNDEFINED : '', + libzfsacl.WHOTYPE_USER_OBJ : 'owner@', + libzfsacl.WHOTYPE_GROUP_OBJ : 'group@', + libzfsacl.WHOTYPE_EVERYONE : 'everyone@', + libzfsacl.WHOTYPE_USER : 'user', + libzfsacl.WHOTYPE_GROUP : 'group' + } + + if who[0] == libzfsacl.WHOTYPE_GROUP: + name = grp.getgrgid(who[1])[0] + elif who[0] == libzfsacl.WHOTYPE_USER: + name = pwd.getpwuid(who[1])[0] + + if who[0] == libzfsacl.WHOTYPE_GROUP or who[0] == libzfsacl.WHOTYPE_USER: + return who_strs[who[0]] + ':' + name + elif who[0] <= libzfsacl.WHOTYPE_EVERYONE: + return who_strs[who[0]] + +def format_to_text(field, to_text): + text = '' + seperator = '' + selector = 1 + skip = '-' + for x in to_text: + if field & x != 0: + text += (to_text[x][selector] + seperator) + else: + text += skip + return text + +def format_perms(permset): + perms_to_text = { + libzfsacl.PERM_READ_DATA : ('read_data', 'r'), + libzfsacl.PERM_WRITE_DATA : ('write_data', 'w'), + libzfsacl.PERM_EXECUTE : ('execute', 'x'), + libzfsacl.PERM_APPEND_DATA : ('append_data', 'p'), + libzfsacl.PERM_DELETE_CHILD : ('delete_child', 'D'), + libzfsacl.PERM_DELETE : ('delete', 'd'), + libzfsacl.PERM_READ_ATTRIBUTES : ('read_attributes', 'a'), + libzfsacl.PERM_WRITE_ATTRIBUTES : ('write_attributes', 'A'), + libzfsacl.PERM_READ_NAMED_ATTRS : ('read_xattr', 'R'), + libzfsacl.PERM_WRITE_NAMED_ATTRS : ('write_xattr', 'W'), + libzfsacl.PERM_READ_ACL : ('read_acl', 'c'), + libzfsacl.PERM_WRITE_ACL : ('write_acl', 'C'), + libzfsacl.PERM_WRITE_OWNER : ('write_owner', 'o'), + libzfsacl.PERM_SYNCHRONIZE : ('synchronize', 's') + } + return format_to_text(permset, perms_to_text) + +def format_flagset(flagset): + flags_to_text = { + libzfsacl.FLAG_FILE_INHERIT : ('file_inherit', 'f'), + libzfsacl.FLAG_DIRECTORY_INHERIT : ('dir_inherit', 'd'), + libzfsacl.FLAG_INHERIT_ONLY : ('inherit_only', 'i'), + libzfsacl.FLAG_NO_PROPAGATE_INHERIT : ('no_propagate', 'n'), + SUCCESSFUL_ACCESS_ACE_FLAG : ('successful_access', 'S'), + FAILED_ACCESS_ACE_FLAG : ('failed_access', 'F'), + libzfsacl.FLAG_INHERITED : ('inherited', 'I'), + } + return format_to_text(flagset, flags_to_text) + +def format_type(etype): + if etype == libzfsacl.ENTRY_TYPE_ALLOW: + return 'allow' + elif etype == libzfsacl.ENTRY_TYPE_DENY: + return 'deny' + +def format_entry(entry): + return { + 'who' : format_who(entry.who), + 'permset' : format_perms(entry.permset), + 'flagset' : format_flagset(entry.flagset), + 'type' : format_type(entry.entry_type) + } + +def print_acl_text(acl, fp, fobj, test): + if test: + print(f'## Test mode only - the resulting ACL for "{fp}":', file=fobj) + else: + if os.path.isdir(fp): + print(f'## Editing NFSv4 ACL for directory: {fp}', file=fobj) + elif os.path.isfile(fp): + print(f'## Editing NFSv4 ACL for file: {fp}', file=fobj) + for i in range (acl.ace_count): + ace = format_entry(acl.get_entry(i)) + print(f"{ace['who']:>18}:{ace['permset']}:{ace['flagset']}:{ace['type']}", + file=fobj) + +def parse_tag(tag): + need_id = False + whotype = -1 + if tag == 'owner@': + whotype = libzfsacl.WHOTYPE_USER_OBJ + elif tag == 'group@': + whotype = libzfsacl.WHOTYPE_GROUP_OBJ + elif tag == 'everyone@': + whotype = libzfsacl.WHOTYPE_EVERYONE + elif tag == 'user' or tag == 'u': + whotype = libzfsacl.WHOTYPE_USER + need_id = True + elif tag == 'group' or tag == 'g': + whotype = libzfsacl.WHOTYPE_GROUP + need_id = True + elif whotype == -1: + print('Malformed ACL: invalid "tag" field', file=sys.stderr) + sys.exit(1) + return (whotype, need_id) + +def parse_id(wtype, name): + if wtype == libzfsacl.WHOTYPE_USER: + try: + id = pwd.getpwnam(name)[2] + except KeyError as e: + print('User ID not found with given user name', file=sys.stderr) + sys.exit(1) + elif wtype == libzfsacl.WHOTYPE_GROUP: + try: + id = grp.getgrnam(name)[2] + except KeyError as e: + print('Group ID not found with given user name', file=sys.stderr) + sys.exit(1) + return id + +def parse_flags(flags, verbose, compact, const): + ret = 0 + if not flags: + return ret + if '/' in flags or flags in verbose: + flags = flags.split('/') + for flag in flags: + if not flag: + continue + if flag in verbose: + ind = verbose.index(flag) + ret |= const[ind] + else: + print(f'Malformed ACL: "{flags}" contains invalid flag "{flag}"', + file=sys.stderr) + sys.exit(1) + elif '-' in flags or list(flags)[0] in compact: + for flag in flags: + if flag == '-': + continue + elif flag in compact: + ind = compact.index(flag) + ret |= const[ind] + else: + print(f'Malformed ACL: "{flags}" contains invalid flag "{flag}"', + file=sys.stderr) + sys.exit(1) + return ret + +def parse_permset(perms): + verbose_perms = [ + 'read_data', + 'write_data', + 'execute', + 'append_data', + 'delete_child', + 'delete', + 'read_attributes', + 'write_attributes', + 'read_xattr', + 'write_xattr', + 'read_acl', + 'write_acl', + 'write_owner', + 'synchronize' + ] + compact_perms = ['r', 'w', 'x', 'p', 'D', 'd', 'a', 'A', 'R', 'W', + 'c', 'C', 'o', 's'] + const_perms = [ + libzfsacl.PERM_READ_DATA, + libzfsacl.PERM_WRITE_DATA, + libzfsacl.PERM_EXECUTE, + libzfsacl.PERM_APPEND_DATA, + libzfsacl.PERM_DELETE_CHILD, + libzfsacl.PERM_DELETE, + libzfsacl.PERM_READ_ATTRIBUTES, + libzfsacl.PERM_WRITE_ATTRIBUTES, + libzfsacl.PERM_READ_NAMED_ATTRS, + libzfsacl.PERM_WRITE_NAMED_ATTRS, + libzfsacl.PERM_READ_ACL, + libzfsacl.PERM_WRITE_ACL, + libzfsacl.PERM_WRITE_OWNER, + libzfsacl.PERM_SYNCHRONIZE + ] + return parse_flags(perms, verbose_perms, compact_perms, const_perms) + +def parse_flagset(flags): + verbose_flags = [ + 'file_inherit', + 'dir_inherit', + 'inherit_only', + 'no_propagate', + 'inherited' + ] + compact_flags = ['f', 'd', 'i', 'n', 'I'] + const_perms = [ + libzfsacl.FLAG_FILE_INHERIT, + libzfsacl.FLAG_DIRECTORY_INHERIT, + libzfsacl.FLAG_INHERIT_ONLY, + libzfsacl.FLAG_NO_PROPAGATE_INHERIT, + libzfsacl.FLAG_INHERITED + ] + return parse_flags(flags, verbose_flags, compact_flags, const_perms) + +def parse_entry_type(etype): + if etype == 'allow': + return libzfsacl.ENTRY_TYPE_ALLOW + elif etype == 'deny': + return libzfsacl.ENTRY_TYPE_DENY + else: + print(f'Invalid entry type: {etype}', file=sys.stderr) + sys.exit(1) + +def parse_json_perms(perms): + ret = 0 + if perms['READ_DATA']: + ret |= libzfsacl.PERM_READ_DATA + if perms['WRITE_DATA']: + ret |= libzfsacl.PERM_WRITE_DATA + if perms['EXECUTE']: + ret |= libzfsacl.PERM_EXECUTE + if perms['APPEND_DATA']: + ret |= libzfsacl.PERM_APPEND_DATA + if perms['DELETE_CHILD']: + ret |= libzfsacl.PERM_DELETE_CHILD + if perms['DELETE']: + ret |= libzfsacl.PERM_DELETE + if perms['READ_ATTRIBUTES']: + ret |= libzfsacl.PERM_READ_ATTRIBUTES + if perms['WRITE_ATTRIBUTES']: + ret |= libzfsacl.PERM_WRITE_ATTRIBUTES + if perms['READ_NAMED_ATTRS']: + ret |= libzfsacl.PERM_READ_NAMED_ATTRS + if perms['WRITE_NAMED_ATTRS']: + ret |= libzfsacl.PERM_WRITE_NAMED_ATTRS + if perms['READ_ACL']: + ret |= libzfsacl.PERM_READ_ACL + if perms['WRITE_ACL']: + ret |= libzfsacl.PERM_WRITE_ACL + if perms['WRITE_OWNER']: + ret |= libzfsacl.PERM_WRITE_OWNER + if perms['SYNCHRONIZE']: + ret |= libzfsacl.PERM_SYNCHRONIZE + return ret + +def parse_json_flags(flags): + ret = 0 + if 'BASIC' in flags: + if flags['BASIC'] == 'NOINHERIT': + return ret + if flags['FILE_INHERIT']: + ret |= libzfsacl.FLAG_FILE_INHERIT + if flags['DIR_INHERIT']: + ret |= libzfsacl.FLAG_DIRECTORY_INHERIT + if flags['INHERIT_ONLY']: + ret |= libzfsacl.FLAG_INHERIT_ONLY + if flags['NO_PROPAGATE']: + ret |= libzfsacl.FLAG_NO_PROPAGATE_INHERIT + if flags['INHERITED']: + ret |= libzfsacl.FLAG_INHERITED + return ret + +def parse_json_acl_flags(flags): + ret = 0 + if flags['AUTOINHERIT']: + ret |= libzfsacl.ACL_AUTO_INHERIT + if flags['DEFAULTED']: + ret |= libzfsacl.ACL_DEFAULT + if flags['PROTECTED']: + ret |= libzfsacl.ACL_PROTECTED + return ret + +def find_ind_by_spec(acl, spec): + for i in range (acl.ace_count): + ace = format_entry(acl.get_entry(i)) + fmt = f"{ace['who']}:{ace['permset']}:{ace['flagset']}:{ace['type']}" + if fmt == spec: + return i + return -1 + +def nfs4acl_sync_mode(acl): + mode = 0 + allow = 0 + deny = 0 + for i in range (acl.ace_count): + entry = acl.get_entry(i) + if entry.entry_type != libzfsacl.ENTRY_TYPE_ALLOW and \ + entry.entry_type != libzfsacl.ENTRY_TYPE_DENY: + print(f'Invalid ACE type: {entry.entry_type}', file=sys.stderr) + continue + + if entry.who[0] == libzfsacl.WHOTYPE_USER_OBJ: + if entry.permset & libzfsacl.PERM_READ_DATA: + if entry.entry_type == libzfsacl.ENTRY_TYPE_ALLOW: + allow |= stat.S_IRUSR + else: + deny |= stat.S_IRUSR + if entry.permset & libzfsacl.PERM_WRITE_DATA: + if entry.entry_type == libzfsacl.ENTRY_TYPE_ALLOW: + allow |= stat.S_IWUSR + else: + deny |= stat.S_IWUSR + if entry.permset & libzfsacl.PERM_EXECUTE: + if entry.entry_type == libzfsacl.ENTRY_TYPE_ALLOW: + allow |= stat.S_IXUSR + else: + deny |= stat.S_IXUSR + + elif entry.who[0] == libzfsacl.WHOTYPE_GROUP_OBJ: + if entry.permset & libzfsacl.PERM_READ_DATA: + if entry.entry_type == libzfsacl.ENTRY_TYPE_ALLOW: + allow |= stat.S_IRGRP + else: + deny |= stat.S_IRGRP + if entry.permset & libzfsacl.PERM_WRITE_DATA: + if entry.entry_type == libzfsacl.ENTRY_TYPE_ALLOW: + allow |= stat.S_IWGRP + else: + deny |= stat.S_IWGRP + if entry.permset & libzfsacl.PERM_EXECUTE: + if entry.entry_type == libzfsacl.ENTRY_TYPE_ALLOW: + allow |= stat.S_IXGRP + else: + deny |= stat.S_IXGRP + + elif entry.who[0] == libzfsacl.WHOTYPE_EVERYONE: + if entry.permset & libzfsacl.PERM_READ_DATA: + if entry.entry_type == libzfsacl.ENTRY_TYPE_ALLOW: + allow |= (stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH) + else: + deny |= (stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH) + if entry.permset & libzfsacl.PERM_WRITE_DATA: + if entry.entry_type == libzfsacl.ENTRY_TYPE_ALLOW: + allow |= (stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH) + else: + deny |= (stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH) + if entry.permset & libzfsacl.PERM_EXECUTE: + if entry.entry_type == libzfsacl.ENTRY_TYPE_ALLOW: + allow |= (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) + else: + deny |= (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) + + mode = allow & ~deny + return mode + +def nfs4acl_from_mode(acl, mode): + user_allow_first = user_allow = user_deny = 0 + group_allow = group_deny = 0 + everyone_allow = 0 + + user_allow = group_allow = everyone_allow = NFS4_ACE_BASE_ALLOW_PSARC + user_allow |= NFS4_ACE_USER_ALLOW_PSARC + if mode & stat.S_IRUSR: + user_allow |= libzfsacl.PERM_READ_DATA + if mode & stat.S_IWUSR: + user_allow |= NFS4_ACE_POSIX_WRITE + if mode & stat.S_IXUSR: + user_allow |= libzfsacl.PERM_EXECUTE + if mode & stat.S_IRGRP: + group_allow |= libzfsacl.PERM_READ_DATA + if mode & stat.S_IWGRP: + group_allow |= NFS4_ACE_POSIX_WRITE + if mode & stat.S_IXGRP: + group_allow |= libzfsacl.PERM_EXECUTE + if mode & stat.S_IROTH: + everyone_allow |= libzfsacl.PERM_READ_DATA + if mode & stat.S_IWOTH: + everyone_allow |= NFS4_ACE_POSIX_WRITE + if mode & stat.S_IXOTH: + everyone_allow |= libzfsacl.PERM_EXECUTE + + user_deny = ((group_allow | everyone_allow) & (~user_allow)) + group_deny = (everyone_allow & (~group_allow)) + user_allow_first = (group_deny & (~user_deny)) + + if user_allow_first != 0: + entry = acl.create_entry() + entry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + entry.permset = user_allow_first + entry.flagset = 0 + entry.who = (libzfsacl.WHOTYPE_USER_OBJ, -1) + + if user_deny != 0: + entry = acl.create_entry() + entry.entry_type = libzfsacl.ENTRY_TYPE_DENY + entry.permset = user_deny + entry.flagset = 0 + entry.who = (libzfsacl.WHOTYPE_USER_OBJ, -1) + + if group_deny != 0: + entry = acl.create_entry() + entry.entry_type = libzfsacl.ENTRY_TYPE_DENY + entry.permset = group_deny + entry.flagset = 0 + entry.who = (libzfsacl.WHOTYPE_GROUP_OBJ, -1) + + entry = acl.create_entry() + entry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + entry.permset = user_allow + entry.flagset = 0 + entry.who = (libzfsacl.WHOTYPE_USER_OBJ, -1) + + entry = acl.create_entry() + entry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + entry.permset = group_allow + entry.flagset = 0 + entry.who = (libzfsacl.WHOTYPE_GROUP_OBJ, -1) + + entry = acl.create_entry() + entry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + entry.permset = everyone_allow + entry.flagset = 0 + entry.who = (libzfsacl.WHOTYPE_EVERYONE, -1) + +def insert_at(acl, ind, spec): + next = 0 + parts = spec.split(':') + if len(parts) not in [4, 5]: + print(f'Invalid ACE provided: {spec}', file=sys.stderr) + return -1 + if ind == acl.ace_count: + entry = acl.create_entry() + else: + entry = acl.create_entry(ind) + whotype, need_id = parse_tag(parts[next]) + next += 1 + whoid = -1 + if need_id: + whoid = parse_id(whotype, parts[next]) + next += 1 + entry.who = (whotype, whoid) + entry.permset = parse_permset(parts[next]) + next += 1 + entry.flagset = parse_flagset(parts[next]) + next +=1 + entry.entry_type = parse_entry_type(parts[next]) + return 0 + +def insert(fp, spec, index, test): + print(f'ace_index: {index} mod_string: {spec}') + acl = libzfsacl.Acl(path=fp) + specs = re.split(r'\s|\t|,', spec) + i = 0 + for s in specs: + if not s: + continue + if insert_at(acl, index + i, s) != 0: + return -1 + i += 1 + if test: + print_acl_text(acl, fp, sys.stdout, test) + else: + acl.setacl(path=fp) + return 0 + +def substitute(fp, spec, test): + acl = libzfsacl.Acl(path=fp) + count = acl.ace_count + if count > 1: + for i in range (count - 1): + acl.delete_entry(0) + specs = re.split(r'\s|\t|,', spec) + for s in reversed(specs): + if not s: + continue + if insert_at(acl, 0, s) != 0: + return -1 + acl.delete_entry(acl.ace_count - 1) + if test: + print_acl_text(acl, fp, sys.stdout, test) + else: + acl.setacl(path=fp) + return 0 + +def remove(fp, spec, test): + acl = libzfsacl.Acl(path=fp) + indices = [] + if spec.isdecimal(): + ind = int(spec) + if ind >= acl.ace_count or ind < 0: + print(f'Index {ind} is out of range ({acl.ace_count} ACEs in ACL)', + file=sys.stderr) + return -1 + indices.append(ind) + else: + specs = re.split(r'\s|\t|,', spec) + for s in specs: + if not s: + continue + ind = find_ind_by_spec(acl, s) + if ind == -1: + print(f'ACL spec: {s} not found', file=sys.stderr) + continue + indices.append(ind) + if len(indices) > 0: + for i in reversed(indices): + acl.delete_entry(i) + if test: + print_acl_text(acl, fp, sys.stdout, test) + else: + acl.setacl(path=fp) + return 0 + else: + return -1 + +def modify(fp, frm, to, test): + acl = libzfsacl.Acl(path=fp) + ind = find_ind_by_spec(acl, frm) + if ind == -1: + print(f'ACL spec: {frm} not found', file=sys.stderr) + return -1 + acl.delete_entry(ind) + if insert_at(acl, ind, to) != 0: + return -1 + if test: + print_acl_text(acl, fp, sys.stdout, test) + else: + acl.setacl(path=fp) + return 0 + +def edit(fp, test): + mktmplt = '.nfs4_setfacl-tmp-' + editor = os.environ.get('EDITOR', 'vi') + tfd, tfname = tempfile.mkstemp(prefix=mktmplt, text=True) + print(tfname) + acl = libzfsacl.Acl(path=fp) + with os.fdopen(tfd, 'w+') as f: + print_acl_text(acl, fp, f, False) + res = subprocess.run([editor, tfname]) + if res.returncode != 0: + print(f'Editor "{editor}" did not exit cleanly, changes will not be saved', + file=sys.stderr) + return -1 + spec = read_acl_spec_from_file(tfname) + os.remove(tfname) + newacl = libzfsacl.Acl() + specs = re.split(r'\s|\t|,', spec) + for s in reversed(specs): + if not s: + continue + if insert_at(newacl, 0, s) != 0: + return -1 + if test: + print_acl_text(newacl, fp, sys.stdout, test) + else: + newacl.setacl(path=fp) + return 0 + +def strip(fp, test): + acl = libzfsacl.Acl(path=fp) + mode = nfs4acl_sync_mode(acl) + newacl = libzfsacl.Acl() + nfs4acl_from_mode(newacl, mode) + if test: + print_acl_text(newacl, fp, sys.stdout, test) + else: + newacl.setacl(path=fp) + return 0 + +def apply_json(fp, jsobj, test): + data = json.loads(jsobj) + newacl = libzfsacl.Acl() + for ace in data['acl']: + entry = newacl.create_entry() + if ace['type'].lower() == 'allow': + entry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + elif ace['type'].lower() == 'deny': + entry.entry_type = libzfsacl.ENTRY_TYPE_DENY + else: + print(f'Invalid entry type: {ace["type"]}', file=sys.stderr) + return -1 + whotype, need_id = parse_tag(ace['tag']) + if need_id: + entry.who = (whotype, int(ace['id'])) + else: + entry.who = (whotype, -1) + entry.permset = parse_json_perms(ace['perms']) + entry.flagset = parse_json_flags(ace['flags']) + newacl.acl_flags = parse_json_acl_flags(data['nfs41_flags']) + if test: + print_acl_text(newacl, fp, sys.stdout, test) + else: + newacl.setacl(path=fp) + return 0 + +def set_flags(fp, flags, test): + flags_to_const = { + 'autoinherit' : libzfsacl.ACL_AUTO_INHERIT, + 'protected' : libzfsacl.ACL_PROTECTED, + 'defaulted' : libzfsacl.ACL_DEFAULT + } + flags = flags.split(',') + rflags = 0 + for flag in flags: + if flag not in flags_to_const: + print(f'Invalid flag: {flag}', file=sys.stderr) + return -1 + else: + rflags |= flags_to_const[flag] + acl = libzfsacl.Acl(path=fp) + acl.acl_flags = rflags + if test: + print_acl_text(acl, fp, sys.stdout, test) + else: + acl.setacl(path=fp) + return 0 + +def operation(action, obj, fp, test): + if action == Action.INSERT: + if (len(obj) == 2): + ind = obj[1] + else: + ind = 0 + return insert(fp, obj[0], ind, test) + elif action == Action.SUBSTITUTE: + return substitute(fp, obj[0], test) + elif action == Action.REMOVE: + return remove(fp, obj[0], test) + elif action == Action.MODIFY: + return modify(fp, obj[0], obj[1], test) + elif action == Action.SET_FLAGS: + return set_flags(fp, obj[0], test) + elif action == Action.EDIT: + return edit(fp, test) + elif action == Action.STRIP: + return strip(fp, test) + elif action == Action.APPLY_JSON: + return apply_json(fp, obj[0], test) + else: + return -1 + +def perform_op(data): + if data['recursive'][0]: + if data['recursive'][1] == WalkType.LOGICAL: + fl = True + else: + fl = False + for (dirpath, subdirs, files) in os.walk(data['file'], followlinks=fl): + for subdir in subdirs: + operation(data['action'], data['object'], dirpath + '/' + subdir, data['test']) + for filename in files: + operation(data['action'], data['object'], dirpath + '/' + filename, data['test']) + operation(data['action'], data['object'], data['file'], data['test']) + return 0 + return operation(data['action'], data['object'], data['file'], data['test']) + +def main(): + data = parse_args() + validate_filepath(data['file']) + if data['specfile']: + data['object'][0] = read_acl_spec_from_file(data['object'][0]) + if perform_op(data) != 0: + sys.exit(1) + sys.exit(0) + +if __name__ == '__main__': + main() diff --git a/contrib/debian/openzfs-zfsutils.install b/contrib/debian/openzfs-zfsutils.install index 546745930bff..db85d879c9b5 100644 --- a/contrib/debian/openzfs-zfsutils.install +++ b/contrib/debian/openzfs-zfsutils.install @@ -30,6 +30,8 @@ sbin/zinject sbin/zpool sbin/zstream sbin/zstreamdump +usr/bin/zfs_getnfs4facl +usr/bin/zfs_setnfs4facl usr/bin/zvol_wait usr/lib/modules-load.d/ lib/ usr/lib/zfs-linux/zpool.d/ diff --git a/rpm/generic/zfs.spec.in b/rpm/generic/zfs.spec.in index 461f6ad84c7f..0f559b66ee5d 100644 --- a/rpm/generic/zfs.spec.in +++ b/rpm/generic/zfs.spec.in @@ -452,7 +452,8 @@ find %{?buildroot}%{_libdir} -name '*.la' -exec rm -f {} \; %if 0%{!?__brp_mangle_shebangs:1} find %{?buildroot}%{_bindir} \ \( -name arc_summary -or -name arcstat -or -name dbufstat \ - -or -name zilstat \) \ + -or -name zilstat -or -name zfs_getnfs4facl \ + -or -name zfs_setnfs4facl \) \ -exec %{__sed} -i 's|^#!.*|#!%{__python}|' {} \; find %{?buildroot}%{_datadir} \ \( -name test-runner.py -or -name zts-report.py \) \ @@ -531,6 +532,8 @@ systemctl --system daemon-reload >/dev/null || true %{_bindir}/arcstat %{_bindir}/dbufstat %{_bindir}/zilstat +%{_bindir}/zfs_getnfs4facl +%{_bindir}/zfs_setnfs4facl # Man pages %{_mandir}/man1/* %{_mandir}/man4/* From 85ee314d34c9b72abad4bcb268221aaf54c70077 Mon Sep 17 00:00:00 2001 From: Umer Saleem Date: Mon, 3 Jul 2023 21:46:32 +0500 Subject: [PATCH 5/5] Add test suite for validating NFSv4.1 ACLs This commit adds test suite for NFSv4.1 ACLS. The test suite uses libzfsacl python bindings to validate functionality of NFS ACLs. The test suite validates the basic behavior of ACLs by verifying default ACEs and then moves to testing all the flags and permissions for deny and allow permissions. Test suite also verifies that allow ACEs don't work without setting the specific permission flag, i.e. to perform an operation, it's permission is required. Similarly, test suite also verifies that allow ACE for a specific permission only allows that perticular permission and user does not have access to other permissions. Signed-off-by: Umer Saleem --- .../debian/openzfs-python3-libzfsacl.install | 1 + lib/libzfsacl/Makefile.am | 3 +- lib/libzfsacl/setup.py.in | 5 +- lib/libzfsacl/zfsacltests/__init__.py | 0 lib/libzfsacl/zfsacltests/test_nfsv4acl.py | 1531 +++++++++++++++++ rpm/generic/zfs.spec.in | 1 + tests/runfiles/common.run | 4 + tests/zfs-tests/tests/Makefile.am | 5 +- .../tests/functional/acl/nfsv4/.gitignore | 1 + .../tests/functional/acl/nfsv4/cleanup.ksh | 34 + .../functional/acl/nfsv4/nfsacl_001.ksh.in | 39 + .../tests/functional/acl/nfsv4/setup.ksh | 46 + 12 files changed, 1666 insertions(+), 4 deletions(-) create mode 100644 lib/libzfsacl/zfsacltests/__init__.py create mode 100644 lib/libzfsacl/zfsacltests/test_nfsv4acl.py create mode 100644 tests/zfs-tests/tests/functional/acl/nfsv4/.gitignore create mode 100755 tests/zfs-tests/tests/functional/acl/nfsv4/cleanup.ksh create mode 100755 tests/zfs-tests/tests/functional/acl/nfsv4/nfsacl_001.ksh.in create mode 100755 tests/zfs-tests/tests/functional/acl/nfsv4/setup.ksh diff --git a/contrib/debian/openzfs-python3-libzfsacl.install b/contrib/debian/openzfs-python3-libzfsacl.install index 2ae33e267c93..9fb7668e15ca 100644 --- a/contrib/debian/openzfs-python3-libzfsacl.install +++ b/contrib/debian/openzfs-python3-libzfsacl.install @@ -1,2 +1,3 @@ usr/lib/python3*/site-packages/libzfsacl-*.egg-info usr/lib/python3*/site-packages/libzfsacl.cpython-*.so +usr/lib/python3*/site-packages/zfsacltests diff --git a/lib/libzfsacl/Makefile.am b/lib/libzfsacl/Makefile.am index 2d63efc27a52..23b7b37b0bc2 100644 --- a/lib/libzfsacl/Makefile.am +++ b/lib/libzfsacl/Makefile.am @@ -1,5 +1,6 @@ dist_noinst_DATA += \ - %D%/libpyzfsacl.c + %D%/libpyzfsacl.c \ + %D%/zfsacltests SUBSTFILES += %D%/setup.py diff --git a/lib/libzfsacl/setup.py.in b/lib/libzfsacl/setup.py.in index ae979a93535d..80a055f23841 100644 --- a/lib/libzfsacl/setup.py.in +++ b/lib/libzfsacl/setup.py.in @@ -18,8 +18,9 @@ setup( version='@VERSION@', description='ACL wrapper library for acessing NFSv4 ACLs on Linux/FreeBSD', ext_modules=[libzfsacl_mod], - packages=find_packages(where=srcdir), - package_dir={"": os.path.relpath(srcdir)}, + packages=find_packages(where=srcdir) + ['zfsacltests'], + package_dir={"": os.path.relpath(srcdir), + "zfsacltests": os.path.relpath(srcdir) + '/zfsacltests'}, include_package_data=True, python_requires='>=3.6,<4', zip_safe=False, diff --git a/lib/libzfsacl/zfsacltests/__init__.py b/lib/libzfsacl/zfsacltests/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/lib/libzfsacl/zfsacltests/test_nfsv4acl.py b/lib/libzfsacl/zfsacltests/test_nfsv4acl.py new file mode 100644 index 000000000000..c258f98e146d --- /dev/null +++ b/lib/libzfsacl/zfsacltests/test_nfsv4acl.py @@ -0,0 +1,1531 @@ +import unittest +import os +import pwd +import shutil +import libzfsacl +import sys +from subprocess import run, PIPE + + +def run_as_user(cmd, user): + if shutil.which(cmd.split()[0]) is not None: + cmd = shutil.which(cmd.split()[0]) + " " + " ".join(cmd.split()[1:]) + command = ["/usr/bin/su", "-", user, "-c", cmd] + proc = run(command, stdout=PIPE, stderr=PIPE, + universal_newlines=True, timeout=30) + if proc.returncode != 0: + return {"result": False, "output": proc.stdout, + "error": proc.stderr, "returncode": proc.returncode} + else: + return {"result": True, "output": proc.stdout, + "error": proc.stderr, "returncode": proc.returncode} + + +class TestNFSAcl(unittest.TestCase): + + ZFS_ACL_STAFF_GROUP = "zfsgrp" + ZFS_ACL_STAFF1 = "staff1" + ZFS_ACL_STAFF2 = "staff2" + ZFS_ACL_STAFF1_UID = 0 + ZFS_ACL_STAFF2_UID = 0 + MOUNTPT = "/var/tmp/testdir" + TESTPOOL = "testpool" + TESTFS = "testfs" + TDIR = '/var/tmp/testdir/test' + USER_OBJ_PERMSET = libzfsacl.PERM_READ_DATA | \ + libzfsacl.PERM_LIST_DIRECTORY | libzfsacl.PERM_WRITE_DATA | \ + libzfsacl.PERM_ADD_FILE | libzfsacl.PERM_APPEND_DATA | \ + libzfsacl.PERM_ADD_SUBDIRECTORY | libzfsacl.PERM_READ_ATTRIBUTES | \ + libzfsacl.PERM_WRITE_ATTRIBUTES | libzfsacl.PERM_READ_NAMED_ATTRS | \ + libzfsacl.PERM_WRITE_NAMED_ATTRS | libzfsacl.PERM_READ_ACL | \ + libzfsacl.PERM_WRITE_ACL | libzfsacl.PERM_WRITE_OWNER | \ + libzfsacl.PERM_SYNCHRONIZE + OMIT_PERMSET = libzfsacl.PERM_READ_DATA | libzfsacl.PERM_WRITE_DATA | \ + libzfsacl.PERM_DELETE_CHILD | libzfsacl.PERM_READ_ATTRIBUTES | \ + libzfsacl.PERM_WRITE_ATTRIBUTES | libzfsacl.PERM_DELETE | \ + libzfsacl.PERM_READ_ACL | libzfsacl.PERM_WRITE_ACL | \ + libzfsacl.PERM_WRITE_OWNER | libzfsacl.PERM_EXECUTE + + # Init UIDs for ZFS users + def __init__(self, *args, **kwargs): + self.ZFS_ACL_STAFF1_UID = pwd.getpwnam(self.ZFS_ACL_STAFF1).pw_uid + self.ZFS_ACL_STAFF2_UID = pwd.getpwnam(self.ZFS_ACL_STAFF2).pw_uid + super(TestNFSAcl, self).__init__(*args, **kwargs) + + # Test pool ACL type is NFSv4 + def test_001_pool_acl_type(self): + acl = libzfsacl.Acl(path=f"/{self.TESTPOOL}") + self.assertEqual(libzfsacl.BRAND_NFSV4, acl.brand, + "ACL type is not NFSv4") + + # Test dataset mountpoint ACL type is NFSv4 + def test_002_fs_acl_type(self): + acl = libzfsacl.Acl(path=self.MOUNTPT) + self.assertEqual(libzfsacl.BRAND_NFSV4, acl.brand, + "ACL type is not NFSv4") + + # Test default ACE count + def test_003_default_ace_count(self): + acl = libzfsacl.Acl(path=self.MOUNTPT) + self.assertEqual(3, acl.ace_count, "Default ace count is not 3") + + # Try to get first ACE + def test_004_get_first_ace(self): + acl = libzfsacl.Acl(path=self.MOUNTPT) + entry0 = acl.get_entry(0) + self.assertEqual(0, entry0.idx, "Failed to get first ACE") + + # Try to get last ACE + def test_005_get_last_ace(self): + acl = libzfsacl.Acl(path=self.MOUNTPT) + entry0 = acl.get_entry(acl.ace_count - 1) + self.assertEqual(acl.ace_count - 1, entry0.idx, + "Failed to get last ACE") + + # Test default USER_OBJ ACE is present + def test_006_default_ace_user_obj(self): + acl = libzfsacl.Acl(path=self.MOUNTPT) + entry0 = acl.get_entry(0) + self.assertEqual(0, entry0.idx, "Default ACE 0 idx is not 0") + self.assertEqual(libzfsacl.ENTRY_TYPE_ALLOW, entry0.entry_type, + "Default ACE 0 is not ENTRY_TYPE_ALLOW") + self.assertEqual(0, entry0.flagset, + "Default ACE 0 flagset is not NO_INHERIT") + self.assertEqual(libzfsacl.WHOTYPE_USER_OBJ, entry0.who[0], + "ACE 0 who type is not USER_OBJ") + self.assertEqual(self.USER_OBJ_PERMSET, + entry0.permset & self.USER_OBJ_PERMSET, + "Default ACE 0 permset does not match" + "USER_OBJ_PERMSET") + + # Test default GROUP_OBJ ACE is present + def test_007_default_ace_group_obj(self): + acl = libzfsacl.Acl(path=self.MOUNTPT) + entry1 = acl.get_entry(1) + self.assertEqual(1, entry1.idx, "Default ACE 1 idx is not 1") + self.assertEqual(libzfsacl.ENTRY_TYPE_ALLOW, entry1.entry_type, + "Default ACE 1 is not ENTRY_TYPE_ALLOW") + self.assertEqual(libzfsacl.WHOTYPE_GROUP_OBJ, entry1.who[0], + "ACE 1 who type is not GROUP_OBJ") + + # Test default EVERYONE ACE is present + def test_008_default_ace_everyone(self): + acl = libzfsacl.Acl(path=self.MOUNTPT) + entry2 = acl.get_entry(2) + self.assertEqual(2, entry2.idx, "Default ACE 2 idx is not 1") + self.assertEqual(libzfsacl.ENTRY_TYPE_ALLOW, entry2.entry_type, + "Default ACE 2 is not ENTRY_TYPE_ALLOW") + self.assertEqual(0, entry2.flagset, + "Default ACE 2 flagset is not NO_INHERIT") + self.assertEqual(libzfsacl.WHOTYPE_EVERYONE, entry2.who[0], + "ACE 2 who type is not EVERYONE") + + # Test an ACE can be appended + def test_009_append_an_ace(self): + os.makedirs(self.TDIR) + dacl = libzfsacl.Acl(path=self.TDIR) + orig_cnt = dacl.ace_count + newEntry = dacl.create_entry() + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + dacl.setacl(path=self.TDIR) + new_cnt = libzfsacl.Acl(path=self.TDIR).ace_count + os.rmdir(self.TDIR) + self.assertEqual(orig_cnt + 1, new_cnt, "Failed to add an ace") + + # Test an ACE can be prepended + def test_010_prepend_an_ace(self): + os.makedirs(self.TDIR) + dacl = libzfsacl.Acl(path=self.TDIR) + orig_cnt = dacl.ace_count + newEntry = dacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + dacl.setacl(path=self.TDIR) + new_cnt = libzfsacl.Acl(path=self.TDIR).ace_count + os.rmdir(self.TDIR) + self.assertEqual(orig_cnt + 1, new_cnt, "Failed to add an ace") + + # Test DENY ace can be set + def test_011_add_ace_set_entry_type_deny(self): + os.makedirs(self.TDIR) + tdacl = libzfsacl.Acl(path=self.TDIR) + newEntry = tdacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_DENY + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + tdacl.setacl(path=self.TDIR) + tdacl_entry0 = libzfsacl.Acl(path=self.TDIR).get_entry(0) + os.rmdir(self.TDIR) + self.assertEqual(libzfsacl.ENTRY_TYPE_DENY, tdacl_entry0.entry_type, + "Failed to add deny ACE") + + # Test ALLOW ace can be set + def test_012_add_ace_set_entry_type_allow(self): + os.makedirs(self.TDIR) + tdacl = libzfsacl.Acl(path=self.TDIR) + newEntry = tdacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + tdacl.setacl(path=self.TDIR) + tdacl_entry0 = libzfsacl.Acl(path=self.TDIR).get_entry(0) + os.rmdir(self.TDIR) + self.assertEqual(libzfsacl.ENTRY_TYPE_ALLOW, tdacl_entry0.entry_type, + "Failed to add allow ACE") + + # Test adding an ACE works on mountpoint + def test_013_add_ace_mountpoint(self): + mpacl = libzfsacl.Acl(path=self.MOUNTPT) + orig_cnt = mpacl.ace_count + newEntry = mpacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_READ_DATA + mpacl.setacl(path=self.MOUNTPT) + self.assertEqual(orig_cnt + 1, mpacl.ace_count, + "Failed to add an ACE on mountpoint") + + # Test removing an ACE works on mountpoint + def test_014_remove_ace_mountpoint(self): + mpacl = libzfsacl.Acl(path=self.MOUNTPT) + orig_cnt = mpacl.ace_count + mpacl.delete_entry(0) + self.assertEqual(orig_cnt - 1, mpacl.ace_count, + "Failed to delete an ACE from mountpoint") + + # Test adding an ACE works on a directory + def test_015_add_ace_dir(self): + os.makedirs(self.TDIR) + dacl = libzfsacl.Acl(path=self.TDIR) + orig_cnt = dacl.ace_count + newEntry = dacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_READ_DATA + dacl.setacl(path=self.TDIR) + self.assertEqual(orig_cnt + 1, dacl.ace_count, + "Failed to add an ACE on a directory") + + # Test removing an ace from a directory + def test_016_remove_ace_dir(self): + dacl = libzfsacl.Acl(path=self.TDIR) + orig_cnt = dacl.ace_count + dacl.delete_entry(0) + new_cnt = dacl.ace_count + os.rmdir(self.TDIR) + self.assertEqual(orig_cnt - 1, new_cnt, + "Failed to delete an ACE from a directory") + + # Test adding an ACE to a file + def test_017_add_ace_file(self): + tfile = f'{self.MOUNTPT}/test.txt' + with open(tfile, 'w'): + pass + facl = libzfsacl.Acl(path=tfile) + orig_cnt = facl.ace_count + newEntry = facl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_READ_DATA + facl.setacl(path=tfile) + self.assertEqual(orig_cnt + 1, facl.ace_count, + "Failed to add an ACE to a file") + + # Test removing an ace from a file + def test_018_remove_ace_file(self): + tfile = f'{self.MOUNTPT}/test.txt' + facl = libzfsacl.Acl(path=tfile) + orig_cnt = facl.ace_count + facl.delete_entry(0) + new_cnt = facl.ace_count + os.remove(tfile) + self.assertEqual(orig_cnt - 1, new_cnt, + "Failed to delete an ACE from a file") + + # Test a flag can be set on file + def test_019_basic_flagset(self): + tfile = f'{self.MOUNTPT}/test.txt' + with open(tfile, 'w'): + pass + facl = libzfsacl.Acl(path=tfile) + newEntry = facl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_READ_DATA + facl.setacl(path=tfile) + facl = libzfsacl.Acl(path=tfile) + facl_entry0 = facl.get_entry(0) + os.remove(tfile) + self.assertEqual(facl_entry0.flagset, 0, + "Failed to set basic flagset") + + # Test multiple flags can be set on directory + def test_020_advanced_flagset(self): + os.makedirs(self.TDIR) + tdacl = libzfsacl.Acl(path=self.TDIR) + adv_flags = libzfsacl.FLAG_FILE_INHERIT | \ + libzfsacl.FLAG_DIRECTORY_INHERIT | \ + libzfsacl.FLAG_NO_PROPAGATE_INHERIT | \ + libzfsacl.FLAG_INHERIT_ONLY + newEntry = tdacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = adv_flags + newEntry.permset = libzfsacl.PERM_READ_DATA + tdacl.setacl(path=self.TDIR) + tdacl = libzfsacl.Acl(path=self.TDIR) + tdacl_entry0 = tdacl.get_entry(0) + os.rmdir(self.TDIR) + self.assertEqual(tdacl_entry0.flagset, adv_flags, + "FLAG_INHERITED is set by default.") + + # Test no inherited ace is present by default + def test_021_flagset_no_inherited_ace_by_default(self): + os.makedirs(self.TDIR) + tdacl = libzfsacl.Acl(path=self.TDIR) + not_inherited = 0 + for i in range(tdacl.ace_count): + if tdacl.get_entry(i).flagset & libzfsacl.FLAG_INHERITED == 0: + not_inherited += 1 + os.rmdir(self.TDIR) + self.assertEqual(not_inherited, tdacl.ace_count, + "FLAG_INHERITED is set by default.") + + # Test FILE_INHERIT flag functions correctly + def test_022_flagset_file_inherit(self): + tfile = f'{self.TDIR}/test_file.txt' + os.makedirs(self.TDIR) + tdacl = libzfsacl.Acl(path=self.TDIR) + newEntry = tdacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = newEntry.flagset | libzfsacl.FLAG_FILE_INHERIT + newEntry.permset = libzfsacl.PERM_READ_DATA + tdacl.setacl(path=self.TDIR) + with open(tfile, 'w'): + pass + tfacl = libzfsacl.Acl(path=tfile) + tfacl_entry0 = tfacl.get_entry(0) + shutil.rmtree(self.TDIR) + self.assertEqual(libzfsacl.FLAG_INHERITED, tfacl_entry0.flagset, + "libzfsacl.FLAG_INHERITED is not set") + + # Test DIRECTORY_INHERIT functions correctly + def test_023_flagset_directory_inherit(self): + tddir = f'{self.TDIR}/test_dir' + os.makedirs(self.TDIR) + tdacl = libzfsacl.Acl(path=self.TDIR) + newEntry = tdacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = newEntry.flagset | libzfsacl.FLAG_DIRECTORY_INHERIT + newEntry.permset = libzfsacl.PERM_READ_DATA + tdacl.setacl(path=self.TDIR) + os.makedirs(tddir) + tfacl = libzfsacl.Acl(path=tddir) + tfacl_entry0 = tfacl.get_entry(0) + shutil.rmtree(self.TDIR) + self.assertEqual(libzfsacl.FLAG_INHERITED | + libzfsacl.FLAG_DIRECTORY_INHERIT, + tfacl_entry0.flagset, + "libzfsacl.FLAG_DIRECTORY_INHERIT is not set") + + # Test NO_PROPAGATE_INHERIT functions correctly + def test_024_flagset_no_propagate_inherit(self): + tddir = f'{self.TDIR}/test_dir' + ttdir = f'{tddir}/test' + os.makedirs(self.TDIR) + tdacl = libzfsacl.Acl(path=self.TDIR) + newEntry = tdacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = newEntry.flagset | \ + libzfsacl.FLAG_DIRECTORY_INHERIT | \ + libzfsacl.FLAG_NO_PROPAGATE_INHERIT + newEntry.permset = libzfsacl.PERM_READ_DATA + tdacl.setacl(path=self.TDIR) + os.makedirs(tddir) + os.makedirs(ttdir) + ttdacl = libzfsacl.Acl(path=ttdir) + not_inherited = 0 + for i in range(ttdacl.ace_count): + if ttdacl.get_entry(i).flagset & libzfsacl.FLAG_INHERITED == 0: + not_inherited += 1 + shutil.rmtree(self.TDIR) + self.assertEqual(ttdacl.ace_count, not_inherited, + "libzfsacl.FLAG_NO_PROPAGATE_INHERIT is not " + "functioning properly") + + # Test INHERIT_ONLY flag behavior on dirs, if DIRECTORY_INHERIT was + # set with INHERIT_ONLY, it is removed from child dirs. If not, + # INHERIT_ONLY should be set on shild dirs. + def test_025_flagset_inherit_only_dir(self): + tddir = f'{self.TDIR}/test_dir' + os.makedirs(self.TDIR) + tdacl = libzfsacl.Acl(path=self.TDIR) + newEntry = tdacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = libzfsacl.FLAG_DIRECTORY_INHERIT | \ + libzfsacl.FLAG_FILE_INHERIT | \ + libzfsacl.FLAG_INHERIT_ONLY + newEntry.permset = libzfsacl.PERM_READ_DATA | \ + libzfsacl.PERM_WRITE_DATA + tdacl.setacl(path=self.TDIR) + os.makedirs(tddir) + tddacl = libzfsacl.Acl(path=tddir) + tdentry0 = tddacl.get_entry(0) + tflags = libzfsacl.FLAG_DIRECTORY_INHERIT | \ + libzfsacl.FLAG_FILE_INHERIT | libzfsacl.FLAG_INHERITED + self.assertEqual(tdentry0.idx, 0, + "Idx of inherited ACE at index 0 should be 0") + self.assertEqual(tdentry0.entry_type, libzfsacl.ENTRY_TYPE_ALLOW, + "Inherited ACE at index 0 should be of type allow") + self.assertEqual(tdentry0.who, + (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID), + "Inherited ACE who is not correct") + self.assertEqual(tdentry0.flagset, tflags, + "Flagset on inherited ACE are not correct") + self.assertEqual(tdentry0.permset, + libzfsacl.PERM_READ_DATA | libzfsacl.PERM_WRITE_DATA, + "Permse of inherited ACE at index 0 are not correct") + os.rmdir(tddir) + tdacl.delete_entry(0) + tdacl.setacl(path=self.TDIR) + newEntry = tdacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = libzfsacl.FLAG_FILE_INHERIT | \ + libzfsacl.FLAG_INHERIT_ONLY + newEntry.permset = libzfsacl.PERM_READ_DATA | \ + libzfsacl.PERM_WRITE_DATA + tdacl.setacl(path=self.TDIR) + os.makedirs(tddir) + tddacl = libzfsacl.Acl(path=tddir) + tdentry0 = tddacl.get_entry(0) + shutil.rmtree(self.TDIR) + tflags = libzfsacl.FLAG_FILE_INHERIT | libzfsacl.FLAG_INHERITED | \ + libzfsacl.FLAG_INHERIT_ONLY + self.assertEqual(tdentry0.idx, 0, + "Idx of inherited ACE at index 0 should be 0") + self.assertEqual(tdentry0.entry_type, libzfsacl.ENTRY_TYPE_ALLOW, + "Inherited ACE at index 0 should be of type allow") + self.assertEqual(tdentry0.who, + (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID), + "Inherited ACE who is not correct") + self.assertEqual(tdentry0.flagset, tflags, + "Flagset on inherited ACE are not correct") + self.assertEqual(tdentry0.permset, + libzfsacl.PERM_READ_DATA | libzfsacl.PERM_WRITE_DATA, + "Permse of inherited ACE at index 0 are not correct") + + # Test INHERIT_ONLY flag behavior on files, ACE should be inheritted + def test_026_flagset_inherit_only_file(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + tdacl = libzfsacl.Acl(path=self.TDIR) + newEntry = tdacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = libzfsacl.FLAG_DIRECTORY_INHERIT | \ + libzfsacl.FLAG_FILE_INHERIT | libzfsacl.FLAG_INHERIT_ONLY + newEntry.permset = libzfsacl.PERM_READ_DATA | libzfsacl.PERM_WRITE_DATA + tdacl.setacl(path=self.TDIR) + with open(tfile, 'w'): + pass + tfacl = libzfsacl.Acl(path=tfile) + tfentry0 = tfacl.get_entry(0) + shutil.rmtree(self.TDIR) + self.assertEqual(tfentry0.idx, 0, + "Idx of inherited ACE at index 0 should be 0") + self.assertEqual(tfentry0.entry_type, libzfsacl.ENTRY_TYPE_ALLOW, + "Inherited ACE at index 0 should be of type allow") + self.assertEqual(tfentry0.who, + (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID), + "Inherited ACE who is not correct") + self.assertEqual(tfentry0.flagset, libzfsacl.FLAG_INHERITED, + "Flagset on inherited ACE are not correct") + self.assertEqual(tfentry0.permset, + libzfsacl.PERM_READ_DATA | libzfsacl.PERM_WRITE_DATA, + "Permse of inherited ACE at index 0 are not correct") + + # Test INHERIT_ONLY flag with NO_PROPAGATE_INHERIT, ACE should be + # inherited but inheritance flags should be removed + def test_027_flagset_no_propagate_dir(self): + tddir = f'{self.TDIR}/test_dir' + os.makedirs(self.TDIR) + tdacl = libzfsacl.Acl(path=self.TDIR) + newEntry = tdacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = libzfsacl.FLAG_DIRECTORY_INHERIT | \ + libzfsacl.FLAG_INHERIT_ONLY | libzfsacl.FLAG_NO_PROPAGATE_INHERIT + newEntry.permset = libzfsacl.PERM_READ_DATA | \ + libzfsacl.PERM_WRITE_DATA + tdacl.setacl(path=self.TDIR) + os.makedirs(tddir) + tddacl = libzfsacl.Acl(path=tddir) + tdentry0 = tddacl.get_entry(0) + shutil.rmtree(self.TDIR) + self.assertEqual(tdentry0.idx, 0, + "Idx of inherited ACE at index 0 should be 0") + self.assertEqual(tdentry0.entry_type, libzfsacl.ENTRY_TYPE_ALLOW, + "Inherited ACE at index 0 should be of type allow") + self.assertEqual(tdentry0.who, + (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID), + "Inherited ACE who is not correct") + self.assertEqual(tdentry0.flagset, libzfsacl.FLAG_INHERITED, + "Flagset on inherited ACE are not correct") + self.assertEqual(tdentry0.permset, + libzfsacl.PERM_READ_DATA | libzfsacl.PERM_WRITE_DATA, + "Permse of inherited ACE at index 0 are not correct") + + # Following test cases verify that deny ACE permsets work correclty. + # Prepend deny ACE denying that particular permission to the the ZFS + # ACL user, then attempt to perform an action that should result in + # failure. + + # Confirm deny ACE works for PERM_READ_DATA. + def test_028_permset_deny_read_data(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w') as file: + file.write("This is a test file.") + tfacl = libzfsacl.Acl(path=tfile) + newEntry = tfacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_DENY + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_READ_DATA + tfacl.setacl(path=tfile) + cmd = f"cat {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], False, "Failed to deny PERM_READ_DATA") + + # Test deny ACE works for PERM_WRITE_DATA + def test_029_permset_deny_write_data(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + tdacl = libzfsacl.Acl(path=self.TDIR) + newEntry = tdacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_DENY + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_WRITE_DATA + tdacl.setacl(path=self.TDIR) + cmd = f"touch {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], False, + "Failed to deny PERM_WRITE_DATA") + + # Test deny ACE works for PERM_EXECUTE + def test_030_permset_deny_execute(self): + os.makedirs(self.TDIR) + tdacl = libzfsacl.Acl(path=self.TDIR) + newEntry = tdacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_DENY + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_EXECUTE + tdacl.setacl(path=self.TDIR) + cmd = f"cd {self.TDIR}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + os.rmdir(self.TDIR) + self.assertEqual(res["result"], False, "Failed to deny PERM_EXECUTE") + + # Test deny ACE works for PERM_READ_ATTRIBUTES + # PERM_READ_ATTRIBUTES is not implemented on Linux. It has no + # equivalent in POSIX ACLs + @unittest.skipIf(sys.platform == 'linux', + "PERM_READ_ATTRIBUTES is not supported for Linux") + def test_031_permset_deny_read_attrs(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w'): + pass + tfacl = libzfsacl.Acl(path=tfile) + newEntry = tfacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_DENY + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_READ_ATTRIBUTES + tfacl.setacl(path=tfile) + cmd = f"stat {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], False, + "Failed to deny PERM_READ_ATTRIBUTES") + + # Test deny ACE works for PERM_WRITE_ATTRIBUTES + def test_032_permset_deny_write_attrs(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w'): + pass + tfacl = libzfsacl.Acl(path=tfile) + newEntry = tfacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_DENY + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_WRITE_ATTRIBUTES + tfacl.setacl(path=tfile) + cmd = f"touch a -m -t 201512180130.09 {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], False, + "Failed to deny PERM_WRITE_ATTRIBUTES") + + # Test deny ACE works for PERM_DELETE + def test_033_permset_deny_delete(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w'): + pass + tfacl = libzfsacl.Acl(path=tfile) + newEntry = tfacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_DENY + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_DELETE + tfacl.setacl(path=tfile) + cmd = f"rm -f {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], False, "Failed to deny PERM_DELETE") + + # Test deny ACE works for PERM_DELETE_CHILD + def test_034_permset_deny_delete_child(self): + tddir = f'{self.TDIR}/test_dir' + os.makedirs(self.TDIR) + os.makedirs(tddir) + tddacl = libzfsacl.Acl(path=tddir) + newEntry = tddacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_DENY + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_DELETE_CHILD + tddacl.setacl(path=tddir) + cmd = f"rm -rf {tddir}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], False, + "Failed to deny PERM_DELETE_CHILD") + + # Test deny ACE works for PERM_READ_ACL + def test_035_permset_deny_read_acl(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w'): + pass + tfacl = libzfsacl.Acl(path=tfile) + newEntry = tfacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_DENY + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_READ_ACL + tfacl.setacl(path=tfile) + cmd = f"zfs_getnfs4facl {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], False, "Failed to deny PERM_READ_ACL") + + # Test deny ACE works for PERM_WRITE_ACL + def test_036_permset_deny_write_acl(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w'): + pass + tfacl = libzfsacl.Acl(path=tfile) + newEntry = tfacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_DENY + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_WRITE_ACL + tfacl.setacl(path=tfile) + cmd = f"zfs_setnfs4facl -x 0 {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], False, "Failed to deny PERM_WRITE_ACL") + + # Test deny ACE works for PERM_WRITE_OWNER + def test_037_permset_deny_write_owner(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w'): + pass + tfacl = libzfsacl.Acl(path=tfile) + newEntry = tfacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_DENY + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_WRITE_OWNER + tfacl.setacl(path=tfile) + cmd = f"chown {self.ZFS_ACL_STAFF1} {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], False, + "Failed to deny PERM_WRITE_OWNER") + + # Test deny ACE works for PERM_ADD_FILE + def test_038_permset_deny_add_file(self): + tddir = f'{self.TDIR}/test_dir' + tfile = f'{self.TDIR}/test_dir/test.txt' + os.makedirs(self.TDIR) + os.makedirs(tddir) + tfacl = libzfsacl.Acl(path=tddir) + newEntry = tfacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_DENY + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_ADD_FILE + tfacl.setacl(path=tddir) + cmd = f"touch {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], False, "Failed to deny PERM_ADD_FILE") + + # Following test cases verify that allow ACE permsets work + # correclty. Prepend allow ACE that allows a particular permission + # to the ZFS ACL user, then attempt to perform an action that should + # result in success. + + # Test allow ACE works for PERM_READ_DATA + def test_039_permset_allow_read_data(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w') as file: + file.write("This is a test file.") + tfacl = libzfsacl.Acl(path=tfile) + newEntry = tfacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_READ_DATA | libzfsacl.PERM_EXECUTE + tfacl.setacl(path=tfile) + cmd = f"cat {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], True, "Failed to allow PERM_READ_DATA") + + # Test allow ACE works for PERM_WRITE_DATA + def test_040_permset_allow_write_data(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w') as file: + file.write("This is a test file.") + tfacl = libzfsacl.Acl(path=tfile) + newEntry = tfacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_WRITE_DATA + tfacl.setacl(path=tfile) + cmd = f'echo -n "CAT" >> {tfile}' + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], True, + "Failed to allow PERM_WRITE_DATA") + + # Test allow ACE works for PERM_EXECUTE + def test_041_permset_allow_execute(self): + os.makedirs(self.TDIR) + tdacl = libzfsacl.Acl(path=self.TDIR) + newEntry = tdacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_EXECUTE + tdacl.setacl(path=self.TDIR) + cmd = f"cd {self.TDIR}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + os.rmdir(self.TDIR) + self.assertEqual(res["result"], True, "Failed to allow PERM_EXECUTE") + + # Test allow ACE works for PERM_READ_ATTRIBUTES + def test_042_permset_allow_read_attrs(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w'): + pass + tfacl = libzfsacl.Acl(path=tfile) + newEntry = tfacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_READ_ATTRIBUTES | \ + libzfsacl.PERM_EXECUTE + tfacl.setacl(path=tfile) + cmd = f"stat {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], True, + "Failed to allow PERM_READ_ATTRIBUTES") + + # Test allow ACE works for PERM_WRITE_ATTRIBUTES + def test_043_permset_allow_write_attrs(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + tfacl = libzfsacl.Acl(path=self.TDIR) + newEntry = tfacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_WRITE_DATA | \ + libzfsacl.PERM_WRITE_ATTRIBUTES + tfacl.setacl(path=self.TDIR) + cmd = f"touch -a -m -t 201512180130.09 {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], True, + "Failed to allow PERM_WRITE_ATTRIBUTES") + + # Test allow ACE works for PERM_DELETE + def test_044_permset_allow_delete(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w'): + pass + tdacl = libzfsacl.Acl(path=self.TDIR) + newEntry = tdacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_DELETE | libzfsacl.PERM_EXECUTE | \ + libzfsacl.PERM_WRITE_DATA + tdacl.setacl(path=self.TDIR) + cmd = f"rm -f {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], True, "Failed to allow PERM_DELETE") + + # Test allow ACE works for PERM_DELETE_CHILD + def test_045_permset_allow_delete_child(self): + tddir = f'{self.TDIR}/test_dir' + os.makedirs(self.TDIR) + os.makedirs(tddir) + os.makedirs(f"{tddir}/tmp") + tfacl = libzfsacl.Acl(path=tddir) + newEntry = tfacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_DELETE_CHILD | \ + libzfsacl.PERM_EXECUTE | libzfsacl.PERM_WRITE_DATA + tfacl.setacl(path=tddir) + cmd = f"rm -rf {tddir}/tmp" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], True, + "Failed to allow PERM_DELETE_CHILD") + + # Test allow ACE works for PERM_READ_ACL + def test_046_permset_allow_read_acl(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w'): + pass + tfacl = libzfsacl.Acl(path=tfile) + newEntry = tfacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_READ_ACL + tfacl.setacl(path=tfile) + cmd = f"zfs_getnfs4facl {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], True, "Failed to allow PERM_READ_ACL") + + # Test allow ACE works for PERM_WRITE_ACL + def test_047_permset_allow_write_acl(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w'): + pass + tfacl = libzfsacl.Acl(path=tfile) + newEntry = tfacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_WRITE_ACL + tfacl.setacl(path=tfile) + cmd = f"zfs_setnfs4facl -a u:{self.ZFS_ACL_STAFF1}:rw-pD-aARWcCos:" + \ + f"-------:allow {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], True, + "Failed to allow PERM_WRITE_ACL") + + # Test allow ACE works for PERM_WRITE_OWNER + # PERM_WRITE_OWNER requires updates in Linux kernel, specifically in + # setattr_prepare(), for permission check for chown and chgrp. + # Without updates in Linux kernel to add permissions check, + # PERM_WRITE_OWNER is not suported on Linux. + @unittest.skipIf(sys.platform == 'linux', + "PERM_WRITE_OWNER is not supported for Linux") + def test_048_permset_allow_write_owner(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w'): + pass + tfacl = libzfsacl.Acl(path=tfile) + newEntry = tfacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_WRITE_OWNER | \ + libzfsacl.PERM_EXECUTE | libzfsacl.PERM_WRITE_DATA | \ + libzfsacl.PERM_READ_ATTRIBUTES | libzfsacl.PERM_WRITE_ATTRIBUTES + tfacl.setacl(path=tfile) + cmd = f"chown {self.ZFS_ACL_STAFF1} {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], True, + "Failed to allow PERM_WRITE_OWNER") + + # Test allow ACE works for PERM_ADD_FILE + def test_049_permset_allow_add_file(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + tdacl = libzfsacl.Acl(path=self.TDIR) + newEntry = tdacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_ADD_FILE + tdacl.setacl(path=self.TDIR) + cmd = f"touch {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], True, "Failed to allow PERM_ADD_FILE") + + # Following test cases verify that allow ACE permsets don't work + # without the specific flag set that is required to perform that + # operation. Prepend allow ACE that allows all permissions, but the + # one that is required to perform a particular operation. This + # should result in failure. + + # Omit PERM_READ_DATA and test reading data + def test_050_permset_omit_read_data(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w') as file: + file.write("This is a test file.") + tfacl = libzfsacl.Acl(path=tfile) + tfacl.delete_entry(2) + newEntry = tfacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = self.OMIT_PERMSET & ~(libzfsacl.PERM_READ_DATA) + tfacl.setacl(path=tfile) + cmd = f"cat {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], False) + + # Omit PERM_WRITE_DATA and test writing data + def test_051_permset_omit_write_data(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w') as file: + file.write("This is a test file.") + tfacl = libzfsacl.Acl(path=tfile) + newEntry = tfacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = self.OMIT_PERMSET & ~(libzfsacl.PERM_WRITE_DATA) + tfacl.setacl(path=tfile) + cmd = f'echo -n "CAT" >> {tfile}' + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], False) + + # Test omit for PERM_EXECUTE + def test_052_permset_omit_execute(self): + os.makedirs(self.TDIR) + tdacl = libzfsacl.Acl(path=self.TDIR) + tdacl.delete_entry(2) + newEntry = tdacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = self.OMIT_PERMSET & ~(libzfsacl.PERM_EXECUTE) + tdacl.setacl(path=self.TDIR) + cmd = f"cd {self.TDIR}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + os.rmdir(self.TDIR) + self.assertEqual(res["result"], False) + + # Test omit for PERM_READ_ATTRIBUTES + # PERM_READ_ATTRIBUTES is not implemented on Linux. It has no + # equivalent in POSIX ACLs + @unittest.skipIf(sys.platform == 'linux', + "PERM_READ_ATTRIBUTES is not implemented for Linux") + def test_053_permset_omit_read_attrs(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w'): + pass + tfacl = libzfsacl.Acl(path=tfile) + tfacl.delete_entry(2) + newEntry = tfacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = self.OMIT_PERMSET & \ + ~(libzfsacl.PERM_READ_ATTRIBUTES) + tfacl.setacl(path=tfile) + cmd = f"stat {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], False) + + # Test omit for PERM_WRITE_ATTRIBUTES + def test_054_permset_omit_write_attrs(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w'): + pass + tfacl = libzfsacl.Acl(path=tfile) + newEntry = tfacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = self.OMIT_PERMSET & \ + ~(libzfsacl.PERM_WRITE_ATTRIBUTES | libzfsacl.PERM_EXECUTE | + libzfsacl.PERM_WRITE_DATA) + tfacl.setacl(path=tfile) + cmd = f"touch a -m -t 201512180130.09 {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], False) + + # Test omit for PERM_DELETE + def test_055_permset_omit_delete(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w'): + pass + tdacl = libzfsacl.Acl(path=self.TDIR) + newEntry = tdacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = self.OMIT_PERMSET & \ + ~(libzfsacl.PERM_DELETE | libzfsacl.PERM_DELETE_CHILD | + libzfsacl.PERM_EXECUTE | libzfsacl.PERM_WRITE_DATA) + tdacl.setacl(path=self.TDIR) + cmd = f"rm -f {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], False) + + # Test omit for PERM_DELETE_CHILD + def test_056_permset_omit_delete_child(self): + tddir = f'{self.TDIR}/test_dir' + os.makedirs(self.TDIR) + os.makedirs(tddir) + os.makedirs(f"{tddir}/tmp") + tfacl = libzfsacl.Acl(path=tddir) + newEntry = tfacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = self.OMIT_PERMSET & \ + ~(libzfsacl.PERM_DELETE_CHILD | libzfsacl.PERM_EXECUTE | + libzfsacl.PERM_WRITE_DATA) + tfacl.setacl(path=tddir) + cmd = f"rm -rf {tddir}/tmp" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], False) + + # Test omit for PERM_READ_ACL + def test_057_permset_omit_read_acl(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w'): + pass + tfacl = libzfsacl.Acl(path=tfile) + tfacl.delete_entry(2) + newEntry = tfacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = self.OMIT_PERMSET & ~(libzfsacl.PERM_READ_ACL) + tfacl.setacl(path=tfile) + cmd = f"zfs_getnfs4facl {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], False) + + # Test omit for PERM_WRITE_ACL + def test_058_permset_omit_write_acl(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w'): + pass + tfacl = libzfsacl.Acl(path=tfile) + newEntry = tfacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = self.OMIT_PERMSET & ~(libzfsacl.PERM_WRITE_ACL) + tfacl.setacl(path=tfile) + cmd = f"zfs_setnfs4facl -x 0 {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], False) + + # Test omit for PERM_WRITE_OWNER + def test_059_permset_omit_write_owner(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w'): + pass + tfacl = libzfsacl.Acl(path=tfile) + newEntry = tfacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = self.OMIT_PERMSET & ~(libzfsacl.PERM_WRITE_OWNER) + tfacl.setacl(path=tfile) + cmd = f"chown {self.ZFS_ACL_STAFF1} {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], False) + + # Test omit for PERM_ADD_FILE + def test_060_permset_omit_add_file(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + tdacl = libzfsacl.Acl(path=self.TDIR) + newEntry = tdacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = self.OMIT_PERMSET & ~(libzfsacl.PERM_ADD_FILE) + tdacl.setacl(path=self.TDIR) + cmd = f"touch {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + shutil.rmtree(self.TDIR) + self.assertEqual(res["result"], False) + + # Following test cases verify that allow ACE permsets only allows a + # user to perform that operation, and user does not have access to + # other permissions. Add and ACE that allows the ZFS ACL user to + # perform an operation, then perform other operations that are not + # permitted to that user. This should result in failure. + + # User is allowed to stat on Linux since, PERM_READ_ATTRIBUTES is not + # implemented on Linux. + + # Test allowing PERM_READ_DATA only allows reading data + def test_061_permset_restrict_read_data(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w') as file: + file.write("This is a test file.") + tfacl = libzfsacl.Acl(path=tfile) + tfacl.delete_entry(2) + newEntry = tfacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_READ_DATA + tfacl.setacl(path=tfile) + cmd = f'echo -n "CAT" >> {tfile}' + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_READ_DATA") + cmd = f"touch a -m -t 201512180130.09 {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_READ_DATA") + cmd = f"rm -f {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_READ_DATA") + cmd = f"chown {self.ZFS_ACL_STAFF1} {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_READ_DATA") + cmd = f"zfs_getnfs4facl {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_READ_DATA") + cmd = f"zfs_setnfs4facl -x 0 {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_READ_DATA") + if sys.platform != 'linux': + cmd = f"stat {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_READ_DATA") + shutil.rmtree(self.TDIR) + + # Test allowing PERM_WRITE_DATA only allows writing data + def test_062_permset_restrict_write_data(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w') as file: + file.write("This is a test file.") + tfacl = libzfsacl.Acl(path=tfile) + tfacl.delete_entry(2) + newEntry = tfacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_WRITE_DATA + tfacl.setacl(path=tfile) + cmd = f"cat {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_WRITE_DATA") + cmd = f"touch a -m -t 201512180130.09 {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_WRITE_DATA") + cmd = f"rm -f {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_WRITE_DATA") + cmd = f"chown {self.ZFS_ACL_STAFF1} {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_WRITE_DATA") + cmd = f"zfs_getnfs4facl {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_WRITE_DATA") + cmd = f"zfs_setnfs4facl -x 0 {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_WRITE_DATA") + if sys.platform != 'linux': + cmd = f"stat {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_WRITE_DATA") + shutil.rmtree(self.TDIR) + + # Test allowing PERM_EXECUTE only allows execution + def test_063_permset_restrict_execute(self): + os.makedirs(self.TDIR) + tfile = f'{self.TDIR}/test.txt' + with open(tfile, 'w'): + pass + tdacl = libzfsacl.Acl(path=self.TDIR) + tdacl.delete_entry(2) + newEntry = tdacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_EXECUTE + tdacl.setacl(path=self.TDIR) + cmd = f"ls {self.TDIR}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_EXECUTE") + cmd = f'echo -n "CAT" >> {tfile}' + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_EXECUTE") + cmd = f"touch a -m -t 201512180130.09 {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_EXECUTE") + cmd = f"rm -f {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_EXECUTE") + cmd = f"chown {self.ZFS_ACL_STAFF1} {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_EXECUTE") + cmd = f"zfs_getnfs4facl {self.TDIR}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_EXECUTE") + cmd = f"zfs_setnfs4facl -x 0 {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_EXECUTE") + shutil.rmtree(self.TDIR) + + # Test allowing PERM_READ_ATTRIBUTES only allows to read attributes + def test_064_permset_restrict_read_attrs(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w'): + pass + tfacl = libzfsacl.Acl(path=tfile) + tfacl.delete_entry(2) + newEntry = tfacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_READ_ATTRIBUTES + tfacl.setacl(path=tfile) + cmd = f"cat {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_READ_ATTRIBUTES") + cmd = f'echo -n "CAT" >> {tfile}' + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_READ_ATTRIBUTES") + cmd = f"touch a -m -t 201512180130.09 {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_READ_ATTRIBUTES") + cmd = f"rm -f {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_READ_ATTRIBUTES") + cmd = f"chown {self.ZFS_ACL_STAFF1} {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_READ_ATTRIBUTES") + cmd = f"zfs_getnfs4facl {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_READ_ATTRIBUTES") + cmd = f"zfs_setnfs4facl -x 0 {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_READ_ATTRIBUTES") + shutil.rmtree(self.TDIR) + + # Test allowing PERM_WRITE_ATTRIBUTES only allows to write attributes + def test_065_permset_restrict_write_attrs(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w'): + pass + tfacl = libzfsacl.Acl(path=tfile) + tfacl.delete_entry(2) + newEntry = tfacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_WRITE_ATTRIBUTES + tfacl.setacl(path=tfile) + cmd = f"cat {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_WRITE_ATTRIBUTES") + cmd = f'echo -n "CAT" >> {tfile}' + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_WRITE_ATTRIBUTES") + cmd = f"rm -f {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_WRITE_ATTRIBUTES") + cmd = f"chown {self.ZFS_ACL_STAFF1} {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_WRITE_ATTRIBUTES") + cmd = f"zfs_getnfs4facl {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_WRITE_ATTRIBUTES") + cmd = f"zfs_setnfs4facl -x 0 {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_WRITE_ATTRIBUTES") + if sys.platform != 'linux': + cmd = f"stat {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_WRITE_ATTRIBUTES") + shutil.rmtree(self.TDIR) + + # Test allowing PERM_DELETE only allows to delete + def test_066_permset_restrict_delete(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w'): + pass + tdacl = libzfsacl.Acl(path=self.TDIR) + tdacl.delete_entry(2) + newEntry = tdacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_DELETE + tdacl.setacl(path=self.TDIR) + cmd = f"ls {self.TDIR}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_DELETE") + cmd = f'echo -n "CAT" >> {tfile}' + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_DELETE") + cmd = f"touch a -m -t 201512180130.09 {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_DELETE") + cmd = f"chown {self.ZFS_ACL_STAFF1} {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_DELETE") + cmd = f"zfs_getnfs4facl {self.TDIR}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_DELETE") + cmd = f"zfs_setnfs4facl -x 0 {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_DELETE") + if sys.platform != 'linux': + cmd = f"stat {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_DELETE") + shutil.rmtree(self.TDIR) + + # Test allowing PERM_READ_ACL only allows to read ACL + def test_067_permset_restrict_read_acl(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w'): + pass + tdacl = libzfsacl.Acl(path=self.TDIR) + tdacl.delete_entry(2) + newEntry = tdacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_READ_ACL + tdacl.setacl(path=self.TDIR) + cmd = f"ls {self.TDIR}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_READ_ACL") + cmd = f'echo -n "CAT" >> {tfile}' + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_READ_ACL") + cmd = f"touch a -m -t 201512180130.09 {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_READ_ACL") + cmd = f"rm -f {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_READ_ACL") + cmd = f"chown {self.ZFS_ACL_STAFF1} {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_READ_ACL") + cmd = f"zfs_setnfs4facl -x 0 {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_READ_ACL") + if sys.platform != 'linux': + cmd = f"stat {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_READ_ACL") + shutil.rmtree(self.TDIR) + + # Test allowing PERM_WRITE_ACL only allows to write ACL + def test_068_permset_restrict_write_acl(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w'): + pass + tdacl = libzfsacl.Acl(path=self.TDIR) + tdacl.delete_entry(2) + newEntry = tdacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_WRITE_ACL + tdacl.setacl(path=self.TDIR) + cmd = f"ls {self.TDIR}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_WRITE_ACL") + cmd = f'echo -n "CAT" >> {tfile}' + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_WRITE_ACL") + cmd = f"touch a -m -t 201512180130.09 {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_WRITE_ACL") + cmd = f"rm -f {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_WRITE_ACL") + cmd = f"chown {self.ZFS_ACL_STAFF1} {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_WRITE_ACL") + cmd = f"zfs_getnfs4facl {self.TDIR}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_WRITE_ACL") + if sys.platform != 'linux': + cmd = f"stat {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_WRITE_ACL") + shutil.rmtree(self.TDIR) + + # Test allowing PERM_WRITE_OWNER only allows to write owner + def test_069_permset_restrict_write_owner(self): + tfile = f'{self.TDIR}/test.txt' + os.makedirs(self.TDIR) + with open(tfile, 'w'): + pass + tfacl = libzfsacl.Acl(path=tfile) + tfacl.delete_entry(2) + newEntry = tfacl.create_entry(0) + newEntry.entry_type = libzfsacl.ENTRY_TYPE_ALLOW + newEntry.who = (libzfsacl.WHOTYPE_USER, self.ZFS_ACL_STAFF1_UID) + newEntry.flagset = 0 + newEntry.permset = libzfsacl.PERM_WRITE_OWNER + tfacl.setacl(path=tfile) + cmd = f"cat {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_WRITE_OWNER") + cmd = f'echo -n "CAT" >> {tfile}' + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_WRITE_OWNER") + cmd = f"touch a -m -t 201512180130.09 {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_WRITE_OWNER") + cmd = f"rm -f {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_WRITE_OWNER") + cmd = f"zfs_getnfs4facl {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_WRITE_OWNER") + cmd = f"zfs_setnfs4facl -x 0 {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_WRITE_OWNER") + if sys.platform != 'linux': + cmd = f"stat {tfile}" + res = run_as_user(cmd, self.ZFS_ACL_STAFF1) + self.assertEqual(res["result"], False, + "Failed to restrict PERM_WRITE_OWNER") + shutil.rmtree(self.TDIR) diff --git a/rpm/generic/zfs.spec.in b/rpm/generic/zfs.spec.in index 0f559b66ee5d..bcde46ea86db 100644 --- a/rpm/generic/zfs.spec.in +++ b/rpm/generic/zfs.spec.in @@ -634,3 +634,4 @@ systemctl --system daemon-reload >/dev/null || true %files -n python%{__python_pkg_version}-libzfsacl %{__python_sitelib}/libzfsacl-*/* %{__python_sitelib}/libzfsacl.cpython*.so +%{__python_sitelib}/zfsacltests/* diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run index 9e186de37369..3d60f60f0412 100644 --- a/tests/runfiles/common.run +++ b/tests/runfiles/common.run @@ -32,6 +32,10 @@ tags = ['functional'] tests = ['dosmode', 'posixmode'] tags = ['functional', 'acl'] +[tests/functional/acl/nfsv4] +tests = ['nfsacl_001'] +tags = ['functional', 'acl', 'nfsv4'] + [tests/functional/alloc_class] tests = ['alloc_class_001_pos', 'alloc_class_002_neg', 'alloc_class_003_pos', 'alloc_class_004_pos', 'alloc_class_005_pos', 'alloc_class_006_pos', diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am index 044a70d1998b..5143363814a3 100644 --- a/tests/zfs-tests/tests/Makefile.am +++ b/tests/zfs-tests/tests/Makefile.am @@ -72,7 +72,8 @@ regen: nobase_nodist_datadir_zfs_tests_tests_DATA = \ functional/pam/utilities.kshlib nobase_nodist_datadir_zfs_tests_tests_SCRIPTS = \ - functional/pyzfs/pyzfs_unittest.ksh + functional/pyzfs/pyzfs_unittest.ksh \ + functional/acl/nfsv4/nfsacl_001.ksh SUBSTFILES += $(nobase_nodist_datadir_zfs_tests_tests_DATA) $(nobase_nodist_datadir_zfs_tests_tests_SCRIPTS) @@ -393,6 +394,8 @@ nobase_dist_datadir_zfs_tests_tests_DATA += \ functional/idmap_mount/idmap_mount_common.kshlib nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \ + functional/acl/nfsv4/cleanup.ksh \ + functional/acl/nfsv4/setup.ksh \ functional/acl/off/cleanup.ksh \ functional/acl/off/dosmode.ksh \ functional/acl/off/posixmode.ksh \ diff --git a/tests/zfs-tests/tests/functional/acl/nfsv4/.gitignore b/tests/zfs-tests/tests/functional/acl/nfsv4/.gitignore new file mode 100644 index 000000000000..ed356dd820b0 --- /dev/null +++ b/tests/zfs-tests/tests/functional/acl/nfsv4/.gitignore @@ -0,0 +1 @@ +nfsacl_001.ksh diff --git a/tests/zfs-tests/tests/functional/acl/nfsv4/cleanup.ksh b/tests/zfs-tests/tests/functional/acl/nfsv4/cleanup.ksh new file mode 100755 index 000000000000..a378434d4afa --- /dev/null +++ b/tests/zfs-tests/tests/functional/acl/nfsv4/cleanup.ksh @@ -0,0 +1,34 @@ +#!/bin/ksh -p +# +# 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 https://opensource.org/licenses/CDDL-1.0. +# 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 2023 iXsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/acl/acl_common.kshlib + +cleanup_user_group + +default_cleanup + diff --git a/tests/zfs-tests/tests/functional/acl/nfsv4/nfsacl_001.ksh.in b/tests/zfs-tests/tests/functional/acl/nfsv4/nfsacl_001.ksh.in new file mode 100755 index 000000000000..5d33bc3b6e7a --- /dev/null +++ b/tests/zfs-tests/tests/functional/acl/nfsv4/nfsacl_001.ksh.in @@ -0,0 +1,39 @@ +#!/bin/ksh -p +# +# 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 https://opensource.org/licenses/CDDL-1.0. +# 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 2023 iXsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/acl/acl_common.kshlib + +verify_runnable "global" +log_assert "Verify NFSv4.1 ACLs behave correctly" + +@PYTHON@ -m unittest --verbose zfsacltests.test_nfsv4acl +if [ $? -ne 0 ]; then + log_fail "NFSv4.1 ACL tests completed with errors" +fi + +log_pass "NFSv4.1 ACL tests completed without errors" diff --git a/tests/zfs-tests/tests/functional/acl/nfsv4/setup.ksh b/tests/zfs-tests/tests/functional/acl/nfsv4/setup.ksh new file mode 100755 index 000000000000..50dc1154b191 --- /dev/null +++ b/tests/zfs-tests/tests/functional/acl/nfsv4/setup.ksh @@ -0,0 +1,46 @@ +#!/bin/ksh -p +# +# 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 https://opensource.org/licenses/CDDL-1.0. +# 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 2023 iXsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/acl/acl_common.kshlib + +cleanup_user_group + +# Create staff group and add user to it +log_must add_group $ZFS_ACL_STAFF_GROUP +log_must add_user $ZFS_ACL_STAFF_GROUP $ZFS_ACL_STAFF1 +log_must add_user $ZFS_ACL_STAFF_GROUP $ZFS_ACL_STAFF2 + +DISK=${DISKS%% *} +default_setup_noexit $DISK +log_must chmod 777 $TESTDIR + +# Use NFSv4 ACLs on filesystem +log_must zfs set acltype=nfsv4 $TESTPOOL +log_must zfs set acltype=nfsv4 $TESTPOOL/$TESTFS + +log_pass