Index: bin/getfacl/getfacl.1 =================================================================== --- bin/getfacl/getfacl.1 +++ bin/getfacl/getfacl.1 @@ -65,6 +65,9 @@ .It Fl h If the target of the operation is a symbolic link, return the ACL from the symbolic link itself rather than following the link. +.It Fl f +For NFSv4 ACLs, add commented infomration about ACL flags. +Ignored for POSIX.1e ACLs. .It Fl i For NFSv4 ACLs, append numerical ID at the end of each entry containing user or group name. @@ -74,7 +77,8 @@ a user or group name. Ignored for POSIX.1e ACLs. .It Fl q -Do not write commented information about file name and ownership. +Do not write commented information about file name, ownership, +and ACL flags. This is useful when dealing with filenames with unprintable characters. .It Fl v Index: bin/getfacl/getfacl.c =================================================================== --- bin/getfacl/getfacl.c +++ bin/getfacl/getfacl.c @@ -175,11 +175,12 @@ } static int -print_acl(char *path, acl_type_t type, int hflag, int iflag, int nflag, - int qflag, int vflag) +print_acl(char *path, acl_type_t type, int hflag, int fflag, int iflag, + int nflag, int qflag, int vflag) { struct stat sb; acl_t acl; + acl_aclflag_t aclflag; char *acl_text; int error, flags = 0, ret; @@ -236,6 +237,24 @@ } } + if (!qflag && fflag) { + ret = acl_get_aclflag_np(acl, &aclflag); + printf("# acl_flag: "); + switch(aclflag) { + case ACL_ACLFLAG_AUTO_INHERIT: + printf("auto-inherit\n"); + break; + case ACL_ACLFLAG_PROTECTED: + printf("protected\n"); + break; + case ACL_ACLFLAG_DEFAULTED: + printf("defaulted\n"); + break; + default: + printf("None\n"); + } + } + if (iflag) flags |= ACL_TEXT_APPEND_ID; @@ -260,8 +279,8 @@ } static int -print_acl_from_stdin(acl_type_t type, int hflag, int iflag, int nflag, - int qflag, int vflag) +print_acl_from_stdin(acl_type_t type, int hflag, int fflag, int iflag, + int nflag, int qflag, int vflag) { char *p, pathname[PATH_MAX]; int carried_error = 0; @@ -269,8 +288,8 @@ while (fgets(pathname, (int)sizeof(pathname), stdin)) { if ((p = strchr(pathname, '\n')) != NULL) *p = '\0'; - if (print_acl(pathname, type, hflag, iflag, nflag, - qflag, vflag) == -1) { + if (print_acl(pathname, type, hflag, fflag, iflag, + nflag, qflag, vflag) == -1) { carried_error = -1; } } @@ -284,14 +303,15 @@ acl_type_t type = ACL_TYPE_ACCESS; int carried_error = 0; int ch, error, i; - int hflag, iflag, qflag, nflag, vflag; + int hflag, iflag, qflag, nflag, vflag, fflag; hflag = 0; iflag = 0; qflag = 0; nflag = 0; vflag = 0; - while ((ch = getopt(argc, argv, "dhinqv")) != -1) + fflag = 0; + while ((ch = getopt(argc, argv, "dfhinqv")) != -1) switch(ch) { case 'd': type = ACL_TYPE_DEFAULT; @@ -299,6 +319,9 @@ case 'h': hflag = 1; break; + case 'f': + fflag = 1; + break; case 'i': iflag = 1; break; @@ -319,20 +342,20 @@ argv += optind; if (argc == 0) { - error = print_acl_from_stdin(type, hflag, iflag, nflag, + error = print_acl_from_stdin(type, hflag, fflag, iflag, nflag, qflag, vflag); return(error ? 1 : 0); } for (i = 0; i < argc; i++) { if (!strcmp(argv[i], "-")) { - error = print_acl_from_stdin(type, hflag, iflag, nflag, - qflag, vflag); + error = print_acl_from_stdin(type, hflag, fflag, iflag, + nflag, qflag, vflag); if (error == -1) carried_error = -1; } else { - error = print_acl(argv[i], type, hflag, iflag, nflag, - qflag, vflag); + error = print_acl(argv[i], type, hflag, fflag, iflag, + nflag, qflag, vflag); if (error == -1) carried_error = -1; } Index: bin/setfacl/Makefile =================================================================== --- bin/setfacl/Makefile +++ bin/setfacl/Makefile @@ -2,6 +2,6 @@ PACKAGE=runtime PROG= setfacl -SRCS= file.c mask.c merge.c remove.c setfacl.c util.c +SRCS= auto_inherit.c file.c mask.c merge.c remove.c setfacl.c util.c .include Index: bin/setfacl/auto_inherit.c =================================================================== --- /dev/null +++ bin/setfacl/auto_inherit.c @@ -0,0 +1,379 @@ +/*- + * Copyright (c) 2020 Andrew Walker + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "setfacl.h" + +bool +auto_inherit_check(char **fl) +{ + bool ok = true; + uint i; + int rv; + acl_aclflag_t acl_flag; + acl_t acl; + + for (i = 0; i < ARRAY_SIZE(fl); i++) { + rv = pathconf(fl[i], _PC_ACL_NFS4); + if (rv == 0) { + warnx("%s: underlying filesystem does not support " + "NFSv4 ACLs", fl[i]); + ok = false; + continue; + } + + if (f_flag) { + /* + * Flag is set to bypass check for PROTECTED. + */ + continue; + } + + acl = acl_get_file(fl[i], ACL_TYPE_NFS4); + if (acl == NULL) { + warnx("%s: acl_get_file() failed.", fl[i]); + ok = false; + continue; + } + + rv = acl_get_aclflag_np(acl, &acl_flag); + if (rv != 0) { + warnx("%s: acl_acl_getaclflag_np() failed.", fl[i]); + ok = false; + acl_free(acl); + continue; + } + + if (acl_flag != ACL_ACLFLAG_PROTECTED) { + warnx("%s: file does not have 'protected' ACL flag set. " + "auto-inheritance not permitted without -f flag.", + fl[i]); + ok = false; + acl_free(acl); + continue; + } + + acl_free(acl); + } + return ok; +} + +/* + * Setting PROTECTED flag is permitted if INHERITED is not present or if the + * -f flag is specified. In case -f flag is specified, then INHERITED flag will + * be stripped from the ACL to mimic behavior of icacls.exe for disabling inheritance + * and replacing inherited ACLs with non-inherited ones. + */ +int +set_aclflag(acl_t acl, acl_aclflag_t new_flag) { + int acl_brand, entry_id; + acl_entry_t entry; + acl_flagset_t acl_flagset; + int has_inherited = 0; + + if (new_flag != ACL_ACLFLAG_PROTECTED) { + return acl_set_aclflag_np(acl, new_flag); + } + + acl_get_brand_np(acl, &acl_brand); + if (acl_brand != ACL_BRAND_NFS4) { + warnx("%0x08x: ACL branding mismatch", acl_brand); + return -1; + } + + entry_id = ACL_FIRST_ENTRY; + while (acl_get_entry(acl, entry_id, &entry) == 1) { + entry_id = ACL_NEXT_ENTRY; + if (acl_get_flagset_np(entry, &acl_flagset) != 0) { + warn("acl_get_flagset_np() failed"); + return -1; + } + has_inherited = acl_get_flag_np(acl_flagset, ACL_ENTRY_INHERITED); + if (has_inherited && !f_flag) { + warnx("ACL contains inherited entries and -f flag " + "not specified."); + return -1; + } + if (acl_delete_flag_np(acl_flagset, ACL_ENTRY_INHERITED) != 0) { + warn("acl_delete_flag_np() failed"); + return -1; + } + } + return acl_set_aclflag_np(acl, new_flag); +} + +/* + * Creates new ACL with INHERITED entries removed. + */ +static acl_t +remove_inherited_entries(acl_t acl, int *cntp) +{ + acl_entry_t entry, new_entry; + int entry_id; + acl_flagset_t flagset; + int has_inherited, acl_count; + acl_t new_acl; + new_acl = acl_init(ACL_MAX_ENTRIES); + + entry_id = ACL_FIRST_ENTRY; + acl_count = 0; + while (acl_get_entry(acl, entry_id, &entry) == 1) { + entry_id = ACL_NEXT_ENTRY; + has_inherited = 0; + if (acl_get_flagset_np(entry, &flagset) != 0) { + err(1, "acl_get_flagset_np() failed"); + } + has_inherited = acl_get_flag_np(flagset, ACL_ENTRY_INHERITED); + if (has_inherited) { + continue; + } + if (acl_create_entry_np(&new_acl, &new_entry, acl_count) == -1) { + err(1, "acl_create_entry() failed"); + } + if (acl_copy_entry(new_entry, entry) == -1) { + err(1, "acl_copy_entry() failed"); + } + acl_count++; + } + *cntp = acl_count; + return new_acl; +} + + +static bool +update_flagset(acl_flagset_t *flagset, bool is_dir) { + /* Entry is not inheritable at all. Skip. */ + int has_inherit, has_np, has_io, rv; + has_inherit = acl_get_flag_np(*flagset, ACL_ENTRY_DIRECTORY_INHERIT | + ACL_ENTRY_FILE_INHERIT); + if (has_inherit == 0){ + return false; + } + + /* + * Skip if the ACE has NO_PROPAGATE flag set and does not have + * INHERIT_ONLY flag. + * Also skip if this is a directory and flags on parent are "fin". + */ + has_np = acl_get_flag_np(*flagset, ACL_ENTRY_NO_PROPAGATE_INHERIT); + if (has_np == 1) { + has_io = acl_get_flag_np(*flagset, ACL_ENTRY_INHERIT_ONLY); + if (has_io == 0) { + return false; + } + rv = acl_get_flag_np(*flagset, ACL_ENTRY_DIRECTORY_INHERIT); + if ((rv == 0) && !is_dir) { + return false; + } + } + + rv = acl_get_flag_np(*flagset, ACL_ENTRY_FILE_INHERIT); + if (rv == 0 && !is_dir) { + return false; + } + + /* + * At this point the entry should be inherited. + * Strip inherit only from the flagset and set + * ACL_ENTRY_INHERITED. + */ + acl_delete_flag_np(*flagset, ACL_ENTRY_INHERIT_ONLY); + acl_add_flag_np(*flagset, ACL_ENTRY_INHERITED); + return true; +} + +static void +add_new_entry(acl_t *new_acl, + acl_entry_t *entry, + acl_flagset_t flagset, + int new_entry_id, + bool is_dir) +{ + int rv; + acl_flagset_t new_flags; + acl_entry_t new_entry; + + rv = acl_create_entry_np(new_acl, &new_entry, new_entry_id); + if (rv == -1) { + err(1, "acl_create_entry() failed"); + } + rv = acl_copy_entry(new_entry, *entry); + if (rv == -1) { + err(1, "acl_create_entry() failed"); + } + + rv = acl_get_flagset_np(new_entry, &new_flags); + if (rv == -1) { + err(1, "acl_get_flagset_np() failed"); + } + + if (!is_dir) { + rv = acl_delete_flag_np(new_flags, + ACL_ENTRY_FILE_INHERIT | + ACL_ENTRY_DIRECTORY_INHERIT | + ACL_ENTRY_NO_PROPAGATE_INHERIT); + if (rv == -1) { + err(1, "acl_delete_flag_np() failed"); + } + return; + } + + rv = acl_get_flag_np(flagset, ACL_ENTRY_NO_PROPAGATE_INHERIT); + if (rv == 1) { + rv = acl_delete_flag_np(new_flags, + ACL_ENTRY_FILE_INHERIT | + ACL_ENTRY_DIRECTORY_INHERIT | + ACL_ENTRY_NO_PROPAGATE_INHERIT); + if (rv == -1) { + err(1, "acl_delete_flag_np() failed"); + } + } +} + +/* + * Returns entries inherited from parent_acl. NULL is returned if no + * inheritable entries are present in parent_acl. + */ +static acl_t +calculate_inherited_acl(char *parent, bool is_dir) +{ + acl_t parent_acl, new_acl; + acl_entry_t entry; + acl_flagset_t flagset; + int entry_id, new_entry_id; + entry_id = ACL_FIRST_ENTRY; + bool ok; + new_entry_id = 0; + + parent_acl = acl_get_file(parent, ACL_TYPE_NFS4); + if (parent_acl == NULL) { + err(1, "%s: acl_get_file() failed.", parent); + } + + new_acl = acl_init(ACL_MAX_ENTRIES); + if (new_acl == NULL) { + err(1, "acl_init() failed"); + } + + while (acl_get_entry(parent_acl, entry_id, &entry) == 1) { + entry_id = ACL_NEXT_ENTRY; + if (acl_get_flagset_np(entry, &flagset)) { + err(1, "acl_get_flagset_np() failed"); + } + ok = update_flagset(&flagset, is_dir); + if (!ok) { + continue; + } + add_new_entry(&new_acl, &entry, flagset, new_entry_id, is_dir); + new_entry_id++; + } + acl_free(parent_acl); + if (new_entry_id == 0) { + warnx("Calculated invalid ACL with no inherited entries."); + acl_free(new_acl); + return (NULL); + } + return (new_acl); +} + +static void +append_acls(acl_t to_add, int index, acl_t *target, bool is_dir) +{ + acl_entry_t entry; + int entry_id; + entry_id = ACL_FIRST_ENTRY; + + while (acl_get_entry(to_add, entry_id, &entry) == 1) { + entry_id = ACL_NEXT_ENTRY; + add_new_entry(target, &entry, 0, index, is_dir); + index++; + } + + if (is_dir) { + acl_set_aclflag_np(*target, ACL_ACLFLAG_AUTO_INHERIT); + } +} + +int +auto_inherit_propagate(acl_t *old_acl, FTSENT *file) { + acl_t to_inherit, new_acl; + int naces; + char parent[PATH_MAX] = {0}; + bool is_dir; + is_dir = (file->fts_info == FTS_D); + + if (file->fts_parent == NULL) { + warnx("fts_parent for [%s] is NULL\n", file->fts_accpath); + return (-1); + } + /* + * fts(3) will chdir into the parent directory of the current FTSENT + * unless FTS_NOCHDIR is set. Hence, retrieving parent directory is + * simply a matter of calling getcwd(3). This reliance on getcwd(3) is + * not symlink safe (it is possible for end-user to create to auto- + * inherit heads for the same path through symlink-following). At + * present this safety net is removed, but in the future a call to + * realpath(3) for file->fts_accpath may warranted in order to ensure + * that inherited ACL is always calculated based on the actual parent + * directory. + */ + if (getcwd(parent, sizeof(parent)) == NULL) { + err(1, "%s: getcwd() failed.", file->fts_accpath); + } + + to_inherit = calculate_inherited_acl(parent, is_dir); + if (to_inherit == NULL) { + warnx("parent directory of [%s] does not contain inheritable " + "entries.", file->fts_accpath); + return (-1); + } + + new_acl = remove_inherited_entries(*old_acl, &naces); + if (new_acl == NULL) { + warnx("failed to strip inherited entries from [%s].", + file->fts_accpath); + acl_free(to_inherit); + return (-1); + } + append_acls(to_inherit, naces, &new_acl, is_dir); + acl_free(*old_acl); + acl_free(to_inherit); + *old_acl = new_acl; + return (0); +} Index: bin/setfacl/setfacl.h =================================================================== --- bin/setfacl/setfacl.h +++ bin/setfacl/setfacl.h @@ -30,10 +30,12 @@ #define _SETFACL_H #include +#include #include #include #include +#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) /* files.c */ acl_t get_acl_from_file(const char *filename); @@ -54,9 +56,15 @@ void *zrealloc(void *ptr, size_t size); const char *brand_name(int brand); int branding_mismatch(int brand1, int brand2); +/* auto_inherit.c */ +bool auto_inherit_check(char **files_list); +int set_aclflag(acl_t acl, acl_aclflag_t new_flag); +int auto_inherit_propagate(acl_t *old_acl, FTSENT *file); + extern bool have_mask; extern bool have_stdin; extern bool n_flag; +extern bool f_flag; #endif /* _SETFACL_H */ Index: bin/setfacl/setfacl.1 =================================================================== --- bin/setfacl/setfacl.1 +++ bin/setfacl/setfacl.1 @@ -82,6 +82,11 @@ Currently only directories may have default ACL's. This option is not applicable to NFSv4 ACLs. +.It Fl f +Force an NFSv4 aclflag change. +This bypasses checks for safety to change the flag. +If forced, inherited flag will be removed from all ACL entries. +This option is only applicable to NFSv4 ACLs. .It Fl h If the target of the operation is a symbolic link, perform the operation on the symbolic link itself, rather than following the link. @@ -91,6 +96,14 @@ option is specified, symbolic links on the command line are followed and hence unaffected by the command. (Symbolic links encountered during tree traversal are not followed.) +.It Fl i Ar aclflag +Change the NFSv41 acl inheritance flag on the given directory. +Supported options for +.Ar aclflag +are "auto-inherit", "protected", "default", "none". +Directories with the "protected" flag will be bypassed during ACL +auto-inheritance operations. +This option is only applicable to NFSv4 ACLs. .It Fl k Delete any default ACL entries on the specified files. It @@ -129,6 +142,17 @@ Do not recalculate the permissions associated with the ACL mask entry. This option is not applicable to NFSv4 ACLs. +.It Fl p +Propagate NFSv41 ACL auto-inheritance on the given paths. +May not be mixed with other flags that modify NFSv4 ACLs. +Implies recursive -R flag. +Directories with PROTECTED flag set are skipped / removed +from the fts tree. +During auto-inheritance propogation, all inherited ACL +entries will be removed from the child ACL and replaced +with newly calculated ACLs based on parent directory. +Root-level directory ACLs will not be altered. +This option is only applicable to NFSv4 ACLs. .It Fl P If the .Fl R Index: bin/setfacl/setfacl.c =================================================================== --- bin/setfacl/setfacl.c +++ bin/setfacl/setfacl.c @@ -50,6 +50,8 @@ #define OP_REMOVE_ACL 0x03 /* remove acl's (-xX) */ #define OP_REMOVE_BY_NUMBER 0x04 /* remove acl's (-xX) by acl entry number */ #define OP_ADD_ACL 0x05 /* add acls entries at a given position */ +#define OP_SET_FLAG 0x06 /* set the ACL flag - V4 only*/ +#define OP_AI_PROPAGATE 0x07 /* propagate ACL based on auto-inheritence rules - V4 only*/ /* TAILQ entry for acl operations */ struct sf_entry { @@ -63,12 +65,14 @@ bool have_mask; bool have_stdin; bool n_flag; +bool f_flag; static bool h_flag; static bool H_flag; static bool L_flag; static bool R_flag; static bool need_mask; static acl_type_t acl_type = ACL_TYPE_ACCESS; +static acl_aclflag_t acl_flag = 0; static int handle_file(FTS *ftsp, FTSENT *file); static acl_t clear_inheritance_flags(acl_t acl); @@ -79,12 +83,23 @@ usage(void) { - fprintf(stderr, "usage: setfacl [-R [-H | -L | -P]] [-bdhkn] " - "[-a position entries] [-m entries] [-M file] " + fprintf(stderr, "usage: setfacl [-R [-H | -L | -P]] [-bdfhkn] " + "[-a position entries] [-m entries] [-M file] [-i flag] " "[-x entries] [-X file] [file ...]\n"); exit(1); } +static const struct { + acl_aclflag_t flag; + const char *flagstr; +} acl_flag_str[] = { + { ACL_ACLFLAG_AUTO_INHERIT, "auto-inherit" }, + { ACL_ACLFLAG_PROTECTED, "protected" }, + { ACL_ACLFLAG_DEFAULTED, "defaulted" }, + { 0, "none" }, +}; + + static char ** stdin_files(void) { @@ -297,6 +312,26 @@ &acl, file->fts_path); need_mask = true; break; + case OP_SET_FLAG: + local_error += set_aclflag(acl, acl_flag); + break; + case OP_AI_PROPAGATE: + /* + * No changes made at root level and branches with + * PROTECTED set are pruned. + */ + if (file->fts_level == FTS_ROOTLEVEL) { + acl_free(acl); + return (0); + } + local_error += acl_get_aclflag_np(acl, &acl_flag); + if (acl_flag == ACL_ACLFLAG_PROTECTED) { + fts_set(ftsp, file, FTS_SKIP); + acl_free(acl); + return (0); + } + local_error += auto_inherit_propagate(&acl, file); + break; } if (nacl != entry->acl) { @@ -351,6 +386,8 @@ main(int argc, char *argv[]) { int carried_error, ch, entry_number, fts_options; + uint i; + bool flag_is_valid, ok, has_autoinherit; FTS *ftsp; FTSENT *file; char **files_list; @@ -359,11 +396,12 @@ acl_type = ACL_TYPE_ACCESS; carried_error = fts_options = 0; - have_mask = have_stdin = n_flag = false; + have_mask = have_stdin = f_flag = n_flag = false; + ok = flag_is_valid = has_autoinherit = false; TAILQ_INIT(&entrylist); - while ((ch = getopt(argc, argv, "HLM:PRX:a:bdhkm:nx:")) != -1) + while ((ch = getopt(argc, argv, "HLM:PRX:a:fi:bdhkpm:nx:")) != -1) switch(ch) { case 'H': H_flag = true; @@ -423,9 +461,30 @@ case 'd': acl_type = ACL_TYPE_DEFAULT; break; + case 'f': + f_flag = true; + break; case 'h': h_flag = 1; break; + case 'i': + entry = zmalloc(sizeof(struct sf_entry)); + for (i = 0; i < ARRAY_SIZE(acl_flag_str); i++) { + if (strcmp(optarg, acl_flag_str[i].flagstr) == 0) { + acl_flag = acl_flag_str[i].flag; + flag_is_valid = true; + break; + } + } + if (!flag_is_valid) { + errno = EINVAL; + err(1, "%s is not a valid ACL flag. " + "Supported flags are: auto-inherit, " + "protected, defaulted, and none.", optarg); + } + entry->op = OP_SET_FLAG; + TAILQ_INSERT_TAIL(&entrylist, entry, next); + break; case 'k': entry = zmalloc(sizeof(struct sf_entry)); entry->op = OP_REMOVE_DEF; @@ -442,6 +501,12 @@ case 'n': n_flag = true; break; + case 'p': + entry = zmalloc(sizeof(struct sf_entry)); + entry->op = OP_AI_PROPAGATE; + TAILQ_INSERT_TAIL(&entrylist, entry, next); + has_autoinherit = true; + break; case 'x': entry = zmalloc(sizeof(struct sf_entry)); entry_number = strtol(optarg, &end, 10); @@ -476,6 +541,20 @@ } else files_list = argv; + if (has_autoinherit) { + ok = auto_inherit_check(files_list); + TAILQ_FOREACH(entry, &entrylist, next) { + if (entry->op != OP_AI_PROPAGATE) { + errx(1, "mixing auto-inherit with other ACL operations. " + "is not permitted."); + } + } + if (!ok) { + errx(1, "auto-inheritance safety check failed."); + } + R_flag = true; + } + if (R_flag) { if (h_flag) errx(1, "the -R and -h options may not be " Index: lib/libc/posix1e/Symbol.map =================================================================== --- lib/libc/posix1e/Symbol.map +++ lib/libc/posix1e/Symbol.map @@ -77,6 +77,8 @@ acl_get_entry_type_np; acl_get_flag_np; acl_get_flagset_np; + acl_get_aclflag_np; + acl_set_aclflag_np; acl_get_perm_np; acl_is_trivial_np; acl_set_entry_type_np; Index: lib/libc/posix1e/acl_flag.c =================================================================== --- lib/libc/posix1e/acl_flag.c +++ lib/libc/posix1e/acl_flag.c @@ -155,3 +155,47 @@ return (0); } + +int +acl_get_aclflag_np(acl_t acl, acl_aclflag_t *aclflag_p) +{ + if (acl == NULL) { + errno = EINVAL; + return (-1); + } + if (_acl_brand(acl) != ACL_BRAND_NFS4) { + errno = EINVAL; + return (-1); + } + + *aclflag_p = acl->ats_acl.acl_flag; + return (0); +} + +int +acl_set_aclflag_np(acl_t acl, acl_aclflag_t aclflag) +{ + if (acl == NULL) { + errno = EINVAL; + return (-1); + } + if (_acl_brand(acl) != ACL_BRAND_NFS4) { + errno = EINVAL; + return (-1); + } + + switch(aclflag) { + case ACL_ACLFLAG_AUTO_INHERIT: + acl->ats_acl.acl_flag = ACL_ACLFLAG_AUTO_INHERIT; + return (0); + case ACL_ACLFLAG_PROTECTED: + acl->ats_acl.acl_flag = ACL_ACLFLAG_PROTECTED; + return (0); + case ACL_ACLFLAG_DEFAULTED: + acl->ats_acl.acl_flag = ACL_ACLFLAG_DEFAULTED; + return (0); + default: + errno = EINVAL; + } + return (-1); +} Index: lib/libc/posix1e/acl_get_aclflag_np.3 =================================================================== --- /dev/null +++ lib/libc/posix1e/acl_get_aclflag_np.3 @@ -0,0 +1,71 @@ +.\"- +.\" Copyright (c) 2020 Andrew Walker +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd August 08, 2020 +.Dt ACL_GET_ACLFLAG_NP 3 +.Os +.Sh NAME +.Nm acl_get_aclflag_np +.Nd return ACL inheritance flag (auto-inherit, protected, defaulted) for an ACL. +.Sh LIBRARY +.Lb libc +.Sh SYNOPSIS +.In sys/types.h +.In sys/acl.h +.Ft int +.Fn acl_get_aclflag_np "acl_t acl" "acl_aclflag_t *aclflag_p" +.Sh DESCRIPTION +The +.Fn acl_get_aclflag_np +function +is a non-portable function that retrieves the ACL inheritance flag from an ACL. +.Sh RETURN VALUES +0 on success +is returned. +.Sh ERRORS +If any of the following conditions occur, the +.Fn acl_get_flag_np +function will return a value of +\-1 +and set global variable +.Va errno +to the corresponding value: +.Bl -tag -width Er +.It Bq Er EINVAL +Argument +.Fa acl +does not contain a valid ACL or ACL has wrong branding (not NFSv4). +.El +.Sh SEE ALSO +.Xr acl 3 , +.Xr acl_set_aclflags_np 3 , +.Xr posix1e 3 +.Sh STANDARDS +POSIX.1e is described in IEEE POSIX.1e draft 17. +.Sh HISTORY +POSIX.1e support was introduced in +.Fx 4.0 . Index: lib/libc/posix1e/acl_set_aclflag_np.3 =================================================================== --- /dev/null +++ lib/libc/posix1e/acl_set_aclflag_np.3 @@ -0,0 +1,73 @@ +.\"- +.\" Copyright (c) 2020 Andrew Walker +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd August 08, 2020 +.Dt ACL_SET_ACLFLAG_NP 3 +.Os +.Sh NAME +.Nm acl_set_aclflag_np +.Nd set an ACL inheritance flag (auto-inherit, protected, defaulted) on an ACL. +.Sh LIBRARY +.Lb libc +.Sh SYNOPSIS +.In sys/types.h +.In sys/acl.h +.Ft int +.Fn acl_set_aclflag_np "acl_t acl" "acl_aclflag_t aclflag" +.Sh DESCRIPTION +The +.Fn acl_set_aclflag_np +function +is a non-portable function that sets an ACL inheritance flag on an ACL. +.Sh RETURN VALUES +0 on success +is returned. +.Sh ERRORS +If any of the following conditions occur, the +.Fn acl_get_flag_np +function will return a value of +\-1 +and set global variable +.Va errno +to the corresponding value: +.Bl -tag -width Er +.It Bq Er EINVAL +Argument +.Fa acl +does not contain a valid ACL or ACL has wrong branding (not NFSv4). +.Fa acl_flag +is not a valid ACL aclflag. +.El +.Sh SEE ALSO +.Xr acl 3 , +.Xr acl_set_aclflags_np 3 , +.Xr posix1e 3 +.Sh STANDARDS +POSIX.1e is described in IEEE POSIX.1e draft 17. +.Sh HISTORY +POSIX.1e support was introduced in +.Fx 4.0 . Index: sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zfs_vnops.c =================================================================== --- sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zfs_vnops.c +++ sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zfs_vnops.c @@ -5833,7 +5833,7 @@ if (ap->a_type != ACL_TYPE_NFS4) return (EINVAL); - vsecattr.vsa_mask = VSA_ACE | VSA_ACECNT; + vsecattr.vsa_mask = VSA_ACE | VSA_ACECNT | VSA_ACE_ACLFLAGS; if (error = zfs_getsecattr(ap->a_vp, &vsecattr, 0, ap->a_cred, NULL)) return (error); @@ -5841,6 +5841,8 @@ if (vsecattr.vsa_aclentp != NULL) kmem_free(vsecattr.vsa_aclentp, vsecattr.vsa_aclentsz); + ap->a_aclp->acl_flag = (acl_aclflag_t)(vsecattr.vsa_aclflags & ACL_ACLFLAGS_ALL); + return (error); } @@ -5884,6 +5886,14 @@ vsecattr.vsa_mask = VSA_ACE; aclbsize = ap->a_aclp->acl_cnt * sizeof(ace_t); vsecattr.vsa_aclentp = kmem_alloc(aclbsize, KM_SLEEP); + /* + * Map acl flags (auto-inherit, protected, defaulted) to vsecattr + * flags and set appropriate mask. + */ + if (ap->a_aclp->acl_flag) { + vsecattr.vsa_aclflags = ap->a_aclp->acl_flag & ACL_FLAGS_ALL; + vsecattr.vsa_mask |= VSA_ACE_ACLFLAGS; + } aaclp = vsecattr.vsa_aclentp; vsecattr.vsa_aclentsz = aclbsize; Index: sys/sys/acl.h =================================================================== --- sys/sys/acl.h +++ sys/sys/acl.h @@ -53,6 +53,7 @@ typedef int acl_type_t; typedef int *acl_permset_t; typedef uint16_t *acl_flagset_t; +typedef int acl_aclflag_t; /* * With 254 entries, "struct acl_t_struct" is exactly one 4kB page big. @@ -120,7 +121,8 @@ unsigned int acl_maxcnt; unsigned int acl_cnt; /* Will be required e.g. to implement NFSv4.1 ACL inheritance. */ - int acl_spare[4]; + int acl_spare[3]; + acl_aclflag_t acl_flag; struct acl_entry acl_entry[ACL_MAX_ENTRIES]; }; @@ -187,6 +189,15 @@ #define ACL_TYPE_DEFAULT 0x00000003 #define ACL_TYPE_NFS4 0x00000004 +/* + * Possible valid values for acl_aclflag_t arguments. + */ +#define ACL_ACLFLAG_AUTO_INHERIT 0x00000001 +#define ACL_ACLFLAG_PROTECTED 0x00000002 +#define ACL_ACLFLAG_DEFAULTED 0x00000004 +#define ACL_ACLFLAGS_ALL (ACL_ACLFLAG_AUTO_INHERIT|ACL_ACLFLAG_PROTECTED| \ + ACL_ACLFLAG_DEFAULTED) + /* * Possible bits in ae_perm field for POSIX.1e ACLs. Note * that ACL_EXECUTE may be used in both NFSv4 and POSIX.1e ACLs. @@ -381,6 +392,8 @@ int acl_free(void *_obj_p); acl_t acl_from_text(const char *_buf_p); int acl_get_brand_np(acl_t _acl, int *_brand_p); +int acl_get_aclflag_np(acl_t _acl, acl_aclflag_t *_aclflag_p); +int acl_set_aclflag_np(acl_t _acl, acl_aclflag_t _aclflag_p); int acl_get_entry(acl_t _acl, int _entry_id, acl_entry_t *_entry_p); acl_t acl_get_fd(int _fd); acl_t acl_get_fd_np(int fd, acl_type_t _type); Index: tests/sys/acl/tools-nfs4-auto-inherit.test =================================================================== --- /dev/null +++ tests/sys/acl/tools-nfs4-auto-inherit.test @@ -0,0 +1,135 @@ +# Copyright (c) 2008, 2009 Edward Tomasz NapieraƂa +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# $FreeBSD$ +# + +# This is a tools-level test for NFSv4.1 ACL auto-inheritance. +# Run it as root using ACL-enabled kernel on ZFS dataset with following properties: +# aclmode = passthrough, aclinherit = passthrough +# +# /usr/src/tools/regression/acltools/run /usr/src/tools/regression/acltools/tools-nfs4-auto-inherit.test +# +# WARNING: Creates files in unsafe way. + +$ whoami +> root +$ umask 022 + +# test setting "protected" flag +$ mkdir test +$ setfacl -m owner@:full_set:fd:allow,group@:full_set:fd:allow -x 2 test +$ setfacl -i protected test +$ getfacl -f test +># file: test +># owner: root +># group: wheel +># acl_flag: protected +> owner@:rwxpDdaARWcCos:fd-----:allow +> group@:rwxpDdaARWcCos:fd-----:allow + +# verify that files are created with no flag set. +$ mkdir -p test/sub1/sub2 +$ touch test/testfile1 test/sub1/testfile2 test/sub1/sub2/testfile3 +$ getfacl -f test/sub1/sub2/testfile3 +># file: test/sub1/sub2/testfile3 +># owner: root +># group: wheel +># acl_flag: None +> owner@:rwxpDdaARWcCos:------I:allow +> group@:rwxpDdaARWcCos:------I:allow + +# verify that "protected" can be set with "-f" flag +# This is also prep step for later test of "setfacl -p" +$ setfacl -f -i protected test/sub1/sub2 +$ getfacl -f test/sub1/sub2 +># file: test/sub1/sub2 +># owner: root +># group: wheel +># acl_flag: protected +> owner@:rwxpDdaARWcCos:fd-----:allow +> group@:rwxpDdaARWcCos:fd-----:allow + +# Add new ACE to force recalculation of inherited ACL +$ setfacl -a 0 u:0:full_set:fd:allow test +$ getfacl -f test +># file: test +># owner: root +># group: wheel +># acl_flag: protected +> user:root:rwxpDdaARWcCos:fd-----:allow +> owner@:rwxpDdaARWcCos:fd-----:allow +> group@:rwxpDdaARWcCos:fd-----:allow + +# Verify that root ACL unchanged after "setfacl -p" +$ setfacl -p test +$ getfacl -f test +># file: test +># owner: root +># group: wheel +># acl_flag: protected +> user:root:rwxpDdaARWcCos:fd-----:allow +> owner@:rwxpDdaARWcCos:fd-----:allow +> group@:rwxpDdaARWcCos:fd-----:allow + +# Verify that subdirectory 1 received ACL and +# "auto-inherit" is set. +$ getfacl -f test/sub1 +># file: test/sub1 +># owner: root +># group: wheel +># acl_flag: auto-inherit +> user:root:rwxpDdaARWcCos:fd----I:allow +> owner@:rwxpDdaARWcCos:fd----I:allow +> group@:rwxpDdaARWcCos:fd----I:allow + +# Verify that subdirectoy 2 was protected from ACL change +$ getfacl -f test/sub1/sub2/ +># file: test/sub1/sub2/ +># owner: root +># group: wheel +># acl_flag: protected +> owner@:rwxpDdaARWcCos:fd-----:allow +> group@:rwxpDdaARWcCos:fd-----:allow + +# Verify that contents of subdirectory 2 was protected. +$ getfacl -f test/sub1/sub2/testfile3 +># file: test/sub1/sub2/testfile3 +># owner: root +># group: wheel +># acl_flag: None +> owner@:rwxpDdaARWcCos:------I:allow +> group@:rwxpDdaARWcCos:------I:allow + +root@:/zroot/TEST # getfacl -f test/testfile1 +# file: test/testfile1 +# owner: root +# group: wheel +# acl_flag: None + user:root:rwxpDdaARWcCos:------I:allow + owner@:rwxpDdaARWcCos:------I:allow + group@:rwxpDdaARWcCos:------I:allow + + +$ rm -r test