diff --git a/cmd/Makefile.am b/cmd/Makefile.am index 6d6de4adb42a..7dbfdbbf7883 100644 --- a/cmd/Makefile.am +++ b/cmd/Makefile.am @@ -100,13 +100,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/config/deb.am b/config/deb.am index 1379e58c40a8..f598ee769188 100644 --- a/config/deb.am +++ b/config/deb.am @@ -61,11 +61,13 @@ deb-utils: deb-local rpm-utils-initramfs pkg4=libzfs5-$${version}.$${arch}.rpm; \ pkg5=libzpool5-$${version}.$${arch}.rpm; \ pkg6=libzfs5-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 98beb900d0fa..9bdf94408605 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-libzfs4 (= ${binary:Version}), openzfs-libzpool5 (= ${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..e1727be550ba --- /dev/null +++ b/contrib/debian/openzfs-python3-libzfsacl.install @@ -0,0 +1,3 @@ +usr/lib/python3/dist-packages/libzfsacl-*.egg-info +usr/lib/python3/dist-packages/libzfsacl.cpython-*.so +usr/lib/python3/dist-packages/zfsacltests diff --git a/contrib/debian/openzfs-python3-pyzfs.install b/contrib/debian/openzfs-python3-pyzfs.install index 4606faae20a7..ada5eca966a4 100644 --- a/contrib/debian/openzfs-python3-pyzfs.install +++ b/contrib/debian/openzfs-python3-pyzfs.install @@ -1 +1,2 @@ -usr/lib/python3* +usr/lib/python3/dist-packages/libzfs_core +usr/lib/python3/dist-packages/pyzfs-*.egg-info diff --git a/contrib/debian/openzfs-zfsutils.install b/contrib/debian/openzfs-zfsutils.install index 741014398ade..06cfef50a69e 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/contrib/debian/rules.in b/contrib/debian/rules.in index a3a05efacb50..51ef151baac5 100755 --- a/contrib/debian/rules.in +++ b/contrib/debian/rules.in @@ -135,6 +135,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 5f38f6ac6ddb..ea2e5c7734ea 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -190,8 +190,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/os/linux/spl/rpc/xdr.h b/include/os/linux/spl/rpc/xdr.h index b00f3542fcdf..05aed7cb81ce 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 typedef int bool_t; 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/os/linux/zfs/sys/zpl.h b/include/os/linux/zfs/sys/zpl.h index 91a4751fffb0..55cdf51c9711 100644 --- a/include/os/linux/zfs/sys/zpl.h +++ b/include/os/linux/zfs/sys/zpl.h @@ -106,6 +106,13 @@ 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); +#else +extern int zpl_permission(struct inode *ip, int mask); +#endif + extern xattr_handler_t *zpl_xattr_handlers[]; /* zpl_ctldir.c */ 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/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/include/zfsacl.h b/include/zfsacl.h new file mode 100644 index 000000000000..8157c5771f5b --- /dev/null +++ b/include/zfsacl.h @@ -0,0 +1,382 @@ +/* + * 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__ extern __attribute__((visibility("default"))) + +#include +#include +#include +#include +#include +#include +#include + +#define zfsace4 zfsacl_entry +typedef unsigned int uint_t; + +/* + * 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 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 64 + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(a) ((int)(sizeof (a)/sizeof (a[0]))) +#endif + +/* + * this is a warning hack. The idea is to use this everywhere that we + * get the "discarding const" warning from gcc. That doesn't actually + * fix the problem of course, but it means that when we do get to + * cleaning them up we can do it by searching the code for + * discard_const. + * + * It also means that other error types aren't as swamped by the noise + * of hundreds of const warnings, so we are more likely to notice when + * we get new errors. + * + * Please only add more uses of this macro when you find it + * _really_ hard to fix const warnings. Our aim is to eventually use + * this function in only a very few places. + * + * Also, please call this via the discard_const_p() macro interface, as that + * makes the return type safe. + */ +#ifndef discard_const +#define discard_const(ptr) ((void *)((uintptr_t)(ptr))) +#endif + +/* + * Type-safe version of discard_const + */ +#ifndef discard_const_p +#define discard_const_p(type, ptr) ((type *)discard_const(ptr)) +#endif + +typedef struct aclflags2name { + zfsacl_aclflags_t flag; + const char *name; +} aclflags2name_t; + +typedef struct aceperms2name { + zfsace_permset_t perm; + const char *name; + char letter; +} aceperms2name_t; + +typedef struct aceflags2name { + zfsace_flagset_t flag; + const char *name; + char letter; +} aceflags2name_t; + +typedef struct aceswho2name { + zfsace_who_t who; + const char *name; +} aceswho2name_t; + +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); + +/* + * 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)) + +#endif /* __ZFSACL_H__ */ diff --git a/lib/Makefile.am b/lib/Makefile.am index 499ebdaeba9b..c7090e192438 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -64,12 +64,15 @@ include $(srcdir)/%D%/libunicode/Makefile.am include $(srcdir)/%D%/libuutil/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..23b7b37b0bc2 --- /dev/null +++ b/lib/libzfsacl/Makefile.am @@ -0,0 +1,21 @@ +dist_noinst_DATA += \ + %D%/libpyzfsacl.c \ + %D%/zfsacltests + +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..ca0e8ce08e0f --- /dev/null +++ b/lib/libzfsacl/libpyzfsacl.c @@ -0,0 +1,1524 @@ +/* + * 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 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, +}; + +aclflags2name_t aclflag2name[] = { + { ZFSACL_AUTO_INHERIT, "AUTO_INHERIT" }, + { ZFSACL_PROTECTED, "PROTECTED" }, + { ZFSACL_DEFAULTED, "DEFAULTED" }, + { ZFSACL_IS_TRIVIAL, "ACL_IS_TRIVIAL" }, + { ZFSACL_IS_DIR, "IS_DIRECTORY" }, +}; + +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); +} + + +aceperms2name_t 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 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); +} + +aceflags2name_t _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 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); +} + +aceswho2name_t 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" }, +}; + +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 = discard_const_p(char, "idx"), + .get = (getter)ace_get_idx, + .doc = py_ace_idx__doc__, + }, + { + .name = discard_const_p(char, "permset"), + .get = (getter)ace_get_permset, + .set = (setter)ace_set_permset, + .doc = py_ace_permset__doc__, + }, + { + .name = discard_const_p(char, "flagset"), + .get = (getter)ace_get_flagset, + .set = (setter)ace_set_flagset, + .doc = py_ace_flagset__doc__, + }, + { + .name = discard_const_p(char, "who"), + .get = (getter)ace_get_who, + .set = (setter)ace_set_who, + .doc = py_ace_who__doc__, + }, + { + .name = discard_const_p(char, "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__ + }, + { 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 = discard_const_p(char, "verbose_output"), + .get = (getter)acl_get_verbose, + .set = (setter)acl_set_verbose, + .doc = py_acl_verbose__doc__, + }, + { + .name = discard_const_p(char, "acl_flags"), + .get = (getter)acl_get_flags, + .set = (setter)acl_set_flags, + .doc = py_acl_flags__doc__, + }, + { + .name = discard_const_p(char, "brand"), + .get = (getter)acl_get_brand, + .doc = py_acl_brand__doc__, + }, + { + .name = discard_const_p(char, "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 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..fbad9b4edf86 --- /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'] +if sys.platform.startswith('linux'): + src = ['zfsacl/libzfsacl_impl_linux.c', 'libpyzfsacl.c'] +else: + src = ['zfsacl/libzfsacl_impl_freebsd.c', 'libpyzfsacl.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) + ['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/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..b16ab69a3aab --- /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; + 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..0acf91e09636 --- /dev/null +++ b/lib/libzfsacl/zfsacl/Makefile.am @@ -0,0 +1,30 @@ +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_linux.c +endif + +if BUILD_FREEBSD +dist_libzfsacl_la_SOURCES = \ + %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_freebsd.c b/lib/libzfsacl/zfsacl/libzfsacl_impl_freebsd.c new file mode 100644 index 000000000000..601bb95037b8 --- /dev/null +++ b/lib/libzfsacl/zfsacl/libzfsacl_impl_freebsd.c @@ -0,0 +1,579 @@ +/* + * 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); + int permset = 0; + 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..abebf7b64c3e --- /dev/null +++ b/lib/libzfsacl/zfsacl/libzfsacl_impl_linux.c @@ -0,0 +1,1000 @@ +/* + * 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 64 +#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 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 + +aceperms2name_t _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 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); +} + +aceflags2name_t 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 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/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/module/os/freebsd/zfs/zfs_acl.c b/module/os/freebsd/zfs/zfs_acl.c index 20466aeaaa05..fb47013d06ef 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/policy.c b/module/os/linux/zfs/policy.c index 5d1b4383412a..a3801c605dd6 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_acl.c b/module/os/linux/zfs/zfs_acl.c index a1fd3c9856cc..ce0edeb1da82 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,21 +1976,21 @@ 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); } + mask = vsecp->vsa_mask & (VSA_ACE | VSA_ACECNT | + VSA_ACE_ACLFLAGS | VSA_ACE_ALLTYPES); + /* * Scan ACL to determine number of ACEs */ @@ -2054,11 +2055,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 +2146,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 +2158,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 +2172,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 +2202,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 +2232,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); } @@ -2523,7 +2620,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 2015c20d7340..dcc586362cc7 100644 --- a/module/os/linux/zfs/zfs_vfsops.c +++ b/module/os/linux/zfs/zfs_vfsops.c @@ -336,9 +336,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; @@ -353,12 +359,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; @@ -367,6 +378,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/zfs_vnops_os.c b/module/os/linux/zfs/zfs_vnops_os.c index b7b89b8afc56..9feea308b9ec 100644 --- a/module/os/linux/zfs/zfs_vnops_os.c +++ b/module/os/linux/zfs/zfs_vnops_os.c @@ -594,6 +594,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); @@ -1220,6 +1236,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)); @@ -1966,10 +1998,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_inode.c b/module/os/linux/zfs/zpl_inode.c index ad1753f7a071..b25f3e9224f3 100644 --- a/module/os/linux/zfs/zpl_inode.c +++ b/module/os/linux/zfs/zpl_inode.c @@ -816,6 +816,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, }; #ifdef HAVE_RENAME2_OPERATIONS_WRAPPER @@ -862,6 +863,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, #ifdef HAVE_RENAME2_OPERATIONS_WRAPPER }, .rename2 = zpl_rename2, @@ -909,4 +911,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 d98d32c1f9fb..670058bd4eda 100644 --- a/module/os/linux/zfs/zpl_super.c +++ b/module/os/linux/zfs/zpl_super.c @@ -230,6 +230,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 4e4f5210f85d..05e76069ced7 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, @@ -250,6 +273,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) @@ -1457,6 +1488,445 @@ 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) +#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) + return (generic_permission(userns, 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) + return (generic_permission(userns, ip, mask)); +#else + return (generic_permission(ip, mask)); +#endif + } + + /* + * 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 ((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); + + /* + * 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 + 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); + } + } + + if (!(mask & MAY_EXEC) || (ip->i_mode & S_IXUGO)) { + 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); + } + + ret = -zfs_access(ITOZ(ip), to_check, V_ACE_MASK, cr); + 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 larger than " + "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; + + /* xattr_resolve_name will do this for us if this is defined */ +#ifndef HAVE_XATTR_HANDLER_NAME + if (strcmp(name, "") != 0) + return (-EINVAL); +#endif + + 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); + } + + if (size < ACES_TO_XDRSIZE(1)) { + return (-EINVAL); + } + + 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(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) }; + + if (ITOZSB(ip)->z_acl_type != ZFS_ACLTYPE_NFSV4) + return (-EOPNOTSUPP); + + 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) { + 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 = +{ +#ifdef HAVE_XATTR_HANDLER_NAME + .name = NFS41ACL_XATTR, +#else + .prefix = NFS41ACL_XATTR, +#endif + .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, @@ -1465,6 +1935,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 }; @@ -1493,6 +1964,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); } diff --git a/rpm/generic/zfs.spec.in b/rpm/generic/zfs.spec.in index 2e89abd0edfd..517ed8e7ce15 100644 --- a/rpm/generic/zfs.spec.in +++ b/rpm/generic/zfs.spec.in @@ -227,6 +227,20 @@ This package provides support for managing ZFS filesystems %postun -n libzfs5 -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 libzfs5-devel Summary: Development headers Group: System Environment/Kernel @@ -346,6 +360,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 @@ -419,7 +449,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 \) \ @@ -498,6 +529,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/* @@ -543,7 +576,13 @@ systemctl --system daemon-reload >/dev/null || true %{_libdir}/libuutil.so.* %files -n libzfs5 -%{_libdir}/libzfs*.so.* +%{_libdir}/libzfs.so.* +%{_libdir}/libzfsbootenv.so.* +%{_libdir}/libzfs_core.so.* + +%files -n libzfsacl1 +%{_libdir}/libzfsacl.so.* +%{_libdir}/libsunacl.so.* %files -n libzfs5-devel %{_pkgconfigdir}/libzfs.pc @@ -587,3 +626,8 @@ 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 +%{__python_sitelib}/zfsacltests/* diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run index 7e0990b5d9f9..341081c11ee8 100644 --- a/tests/runfiles/common.run +++ b/tests/runfiles/common.run @@ -28,6 +28,10 @@ failsafe = callbacks/zfs_failsafe outputdir = /var/tmp/test_results tags = ['functional'] +[tests/functional/acl/nfsv4] +tests = ['nfsacl_001'] +tags = ['functional', 'acl', 'nfsv4'] + [tests/functional/acl/off] tests = ['dosmode', 'posixmode'] tags = ['functional', 'acl'] diff --git a/tests/runfiles/sanity.run b/tests/runfiles/sanity.run index ab41c05b8473..796c43cd3d2b 100644 --- a/tests/runfiles/sanity.run +++ b/tests/runfiles/sanity.run @@ -30,6 +30,10 @@ failsafe = callbacks/zfs_failsafe outputdir = /var/tmp/test_results tags = ['functional'] +[tests/functional/acl/nfsv4] +tests = ['nfsacl_001'] +tags = ['functional', 'acl', 'nfsv4'] + [tests/functional/acl/off] tests = ['posixmode'] tags = ['functional', 'acl'] diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am index 4040e60434a7..197aecc89f84 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) @@ -390,6 +391,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