smbcacls support for automatic inheritance propagation
Noel Power
nopower at suse.com
Tue Feb 7 18:43:31 UTC 2017
On 04/02/17 13:49, Ralph Böhme wrote:
> Hi Noel,
>
> O
[...]
> Fmpov please cleanup first and squash first, I'd prefer to look at what you
> believe to be the most sensible patchset then looking at a WIP state. :)
Alrighty :-) please see attached and same can be viewed at
https://cgit.freedesktop.org/~noelp/noelp-samba/log/?h=smbcacls_review%236
thanks alot
Noel
-------------- next part --------------
From 4ba0ae78cc66165c1c641e1c4de7cecac4b9b709 Mon Sep 17 00:00:00 2001
From: David Disseldorp <ddiss at samba.org>
Date: Thu, 14 Nov 2013 16:10:26 +0000
Subject: [PATCH 1/4] add smbcacls test based on test_smbclient_tarmode.pl
Pair-Programmed-With: Noel Power <noel.power at suse.com>
Signed-off-by: Noel Power <noel.power at suse.com>
---
selftest/selftesthelpers.py | 1 +
source3/script/tests/test_smbcacls.pl | 3227 +++++++++++++++++++++++++++++++++
source3/selftest/tests.py | 22 +
3 files changed, 3250 insertions(+)
create mode 100755 source3/script/tests/test_smbcacls.pl
diff --git a/selftest/selftesthelpers.py b/selftest/selftesthelpers.py
index b0ece36..f83236a 100644
--- a/selftest/selftesthelpers.py
+++ b/selftest/selftesthelpers.py
@@ -184,6 +184,7 @@ def smbtorture4_testsuites(prefix):
smbclient3 = binpath('smbclient')
+smbcacls = binpath('smbcacls')
smbtorture3 = binpath('smbtorture3')
ntlm_auth3 = binpath('ntlm_auth')
net = binpath('net')
diff --git a/source3/script/tests/test_smbcacls.pl b/source3/script/tests/test_smbcacls.pl
new file mode 100755
index 0000000..41bbc0b
--- /dev/null
+++ b/source3/script/tests/test_smbcacls.pl
@@ -0,0 +1,3227 @@
+#!/usr/bin/perl
+
+# Unix SMB/CIFS implementation.
+# Test suite for the smbcacls binary
+# Copyright (C) Aurélien Aptel 2013
+# Copyright (C) David Disseldorp 2013
+# Copyright (C) Noel Power 2013
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# TODO - split all common test_smbcacls.pl/test_smbclient_tarmode.pl code into
+# a separate file.
+
+=head1 NAME
+
+C<test_smbcacls.pl> - Test for smbcacls
+
+=cut
+
+use v5.10;
+use strict;
+use warnings;
+
+use Archive::Tar;
+use Data::Dumper;
+use Digest::MD5 qw/md5_hex/;
+use File::Path qw/make_path remove_tree/;
+use File::Spec;
+use File::Temp;
+use Getopt::Long;
+use Pod::Usage;
+use Term::ANSIColor;
+
+sub d {print Dumper @_;}
+
+# DEFAULTS
+# 'our' to make them available in the File package
+our $USER = '';
+our $PW = '';
+our $HOST = '';
+our $SHARE = '';
+our $DIR = 'tar_test_dir';
+our $LOCALPATH = '';
+our $TMP = File::Temp->newdir();
+our $CLI_BIN = 'smbclient';
+our $CACLS_BIN = 'smbcacls';
+our $SUBUNIT = 0;
+
+my $SELECTED_TEST = '';
+my $LIST_TEST = 0;
+
+my @SMBARGS = ();
+
+our $DEBUG = 1;
+our $VERBOSE = 1;
+my $MAN = 0;
+my $HELP = 0;
+my $CLEAN = 0;
+
+# all tests
+my @TESTS = (
+ ['simple, single file set ACL', \&test_simple_single_set],
+ ['simple, single file modify ACL', \&test_simple_single_mod],
+ ['simple, single file delete ACL', \&test_simple_single_del],
+ ['simple, single file add ACL', \&test_simple_single_add],
+ ['simple, single object inherit add', \&test_simple_oi_add],
+ ['simple, single object inherit delete', \&test_simple_oi_delete],
+ ['simple, single object inherit modify', \&test_simple_oi_modify],
+ ['simple, single container inherit add', \&test_simple_ci_add],
+ ['simple, single container inherit delete', \&test_simple_ci_delete],
+ ['simple, single container inherit modify', \&test_simple_ci_modify],
+ ['simple, single container/object inherit add', \&test_simple_cioi_add],
+ ['simple, single container/object inherit delete', \&test_simple_cioi_delete],
+ ['simple, single container/object inherit modify', \&test_simple_cioi_modify],
+ ['simple, single inherit set(fail test)', \&test_simple_set_fail],
+ ['simple, single container/object inherit set', \&test_simple_oici_set],
+ ['simple, single container inherit set', \&test_simple_ci_set],
+ ['simple, single container/inherit/np add', \&test_simple_cioinp_add],
+ ['simple, single object inherit/np add', \&test_simple_oinp_add],
+ ['simple, single container inherit/np add', \&test_simple_cinp_add],
+ ['simple, single container/inherit/np delete', \&test_simple_cioinp_delete],
+ ['simple, single object inherit/np delete', \&test_simple_oinp_delete],
+ ['simple, single container inherit/np delete', \&test_simple_cinp_delete],
+ ['simple, single container/object inherit inhibit', \&test_simple_cioi_inhibit],
+);
+
+=head1 SYNOPSIS
+
+ test_smbcacls.pl [options] -- [smbclient options]
+
+ Options:
+ -h, --help brief help message
+ --man full documentation
+
+ Environment:
+ -u, --user USER
+ -p, --password PW
+ -n, --name HOST (required)
+ -s, --share SHARE (required)
+ -d, --dir PATH
+ sub-path to use on the share
+
+ -l, --local-path PATH (required)
+ path to the root of the samba share on the machine.
+
+ --cli_bin BIN
+ path to the smbclient binary to use
+
+ --cacls_bin BIN
+ path to the smbcacls binary to use
+
+ Test:
+ --list
+ list tests
+
+ --test N
+ --test A-B
+ --test A,B,D-F
+ only run certain tests (accept list and intervals of numbers)
+
+ -v, --verbose
+ be more verbose
+
+ --debug
+ print command and their output (also set -v)
+
+ --subunit
+ print output in subunit format
+
+ --clean
+ clean out DIR path between tests
+
+=cut
+
+GetOptions('u|user=s' => \$USER,
+ 'p|password=s' => \$PW,
+ 'n|name=s' => \$HOST,
+ 's|share=s' => \$SHARE,
+ 'd|dir=s' => \$DIR,
+ 'l|local-path=s' => \$LOCALPATH,
+ 'cli_bin=s' => \$CLI_BIN,
+ 'cacls_bin=s' => \$CACLS_BIN,
+
+ 'test=s' => \$SELECTED_TEST,
+ 'list' => \$LIST_TEST,
+
+ 'clean' => \$CLEAN,
+ 'subunit' => \$SUBUNIT,
+ 'debug' => \$DEBUG,
+ 'v|verbose' => \$VERBOSE,
+ 'h|help' => \$HELP,
+ 'man' => \$MAN) or pod2usage(2);
+
+pod2usage(0) if $HELP;
+pod2usage(-exitval => 0, -verbose => 2) if $MAN;
+list_test(), exit 0 if $LIST_TEST;
+pod2usage(1) unless $HOST;
+pod2usage(1) unless $SHARE;
+pod2usage(1) unless $LOCALPATH;
+pod2usage(1) unless $USER;
+pod2usage(1) unless $PW;
+
+push @SMBARGS, '-U'.$USER.'%'.$PW;
+
+# remaining arguments are passed to smbclient
+push @SMBARGS, @ARGV;
+
+#####
+
+# SANITIZATION
+
+# remove all final slashes from input paths
+$LOCALPATH =~ s{[/\\]+$}{}g;
+$SHARE =~ s{[/\\]+$}{}g;
+$HOST =~ s{[/\\]+$}{}g;
+$DIR =~ s{[/\\]+$}{}g;
+# remove ./ prefix from DIR
+$DIR =~ s{^[\./\\]+}{}g;
+
+if (!-d $LOCALPATH) {
+ die "Local path '$LOCALPATH' is not a directory.\n";
+}
+
+if ($CLEAN) {
+ # clean the whole root first
+ remove_tree($LOCALPATH, { keep_root => 1 });
+}
+
+if ($DEBUG) {
+ $VERBOSE = 1;
+}
+
+#####
+
+# RUN TESTS
+
+my @selection = parse_test_string($SELECTED_TEST);
+
+if ($SELECTED_TEST eq '') {
+ run_test(@TESTS);
+} elsif (@selection > 0) {
+ run_test(@selection);
+} else {
+ die "Test selection '$SELECTED_TEST' is invalid\n";
+}
+
+#################################
+
+=head1 DOCUMENTATION
+
+=head2 Defining a test
+
+=over
+
+=item * Create a function C<test_yourtest>
+
+=item * Use the File module, documented below
+
+=item * Use C<smb_tar>, C<smb_client>, C<smb_cacls>, C<check_tar> or C<check_remote>
+
+=item * Return number of errors
+
+=item * Add function to C<@TESTS>
+
+=back
+
+The function must be placed in the C<@TESTS> list along with a short
+description and optional arguments.
+
+=cut
+
+
+# test smbcacls '--set' attempts to overwrite the ACL for the file
+#
+# before:
+#
+# +-tar_test_dir/
+# +-file.1 (I)(F)
+#
+# after/expected:
+#
+# +-tar_test_dir/
+# +-file.1 (F)
+#
+
+sub test_simple_single_set
+{
+ my @files;
+ my $acl_str = "ACL:$USER:ALLOWED/0x0/FULL";
+ my $ace;
+ my $ret;
+ my $out;
+
+ my $f = File->new_local("$TMP/file-1");
+ $f->put_remote("file-1");
+
+ ($out, $ret) = smb_cacls('--set', $acl_str, $f->smb_remotepath);
+ if ($ret != 0 ) {
+ return 1;
+ }
+ # only a single ACE string in the ACL
+ $ace = ace_parse_str($acl_str);
+ $ret = file_ace_check($f->smb_remotepath, $ace);
+
+ $f->del_remote(1);
+
+ return 0;
+}
+
+
+# test smbcacls '--modify' attempts to modify the ACL for the file
+# (note: first part of the test 'set' ACL to (F) then attempts to modify
+# before:
+#
+# +-tar_test_dir/
+# +-file.1 (F)
+#
+# after/expected:
+#
+# +-tar_test_dir/
+# +-file.1 (READ)
+#
+sub test_simple_single_mod
+{
+ my @files;
+ my $acl_str = "ACL:$USER:ALLOWED/0x0/FULL";
+ my $ace;
+ my $ret;
+ my $out;
+
+ my $f = File->new_local("$TMP/file-1");
+ $f->put_remote("file-1");
+
+ ($out, $ret) = smb_cacls('--set', $acl_str, $f->smb_remotepath);
+ if ($ret != 0 ) {
+ return 1;
+ }
+ # only a single ACE string in the ACL
+ $ace = ace_parse_str($acl_str);
+ $ret = file_ace_check($f->smb_remotepath, $ace);
+ if ($ret != 0) {
+ return 1;
+ }
+
+ # overwrite existing entry
+ $acl_str = "ACL:$USER:ALLOWED/0x0/READ";
+ ($out, $ret) = smb_cacls('--modify', $acl_str, $f->smb_remotepath);
+ if ($ret != 0) {
+ return 1;
+ }
+ $ace = ace_parse_str($acl_str);
+ $ret = file_ace_check($f->smb_remotepath, $ace);
+
+ $f->del_remote(1);
+
+ return 0;
+}
+
+# test smbcacls '--delete' attempts to delete the ACL for the file
+# (note: first part of the test 'set' ACL to (F) then attempts to delete
+# before:
+#
+# +-tar_test_dir/
+# +-file.1 (F)
+#
+# after/expected:
+#
+# +-tar_test_dir/
+# +-file.1 (none) - meaning no (F) ACL for this user
+#
+sub test_simple_single_del
+{
+ my @files;
+ my $acl_str = "ACL:$USER:ALLOWED/0x0/FULL";
+ my $ace;
+ my $ret;
+ my $out;
+ my $f = File->new_local("$TMP/file-1");
+ $f->put_remote("file-1");
+
+ smb_cacls('--set', $acl_str, $f->smb_remotepath);
+
+ # only a single ACE string in the ACL
+ $ace = ace_parse_str($acl_str);
+ $ret = file_ace_check($f->smb_remotepath, $ace);
+ if ($ret != 0) {
+ return 1;
+ }
+
+ ($out, $ret) = smb_cacls('--delete', $acl_str, $f->smb_remotepath);
+ if ($ret != 0 ) {
+ return 1;
+ }
+ $ace = ace_parse_str($acl_str);
+ $ret = file_ace_check($f->smb_remotepath, $ace);
+ if ($ret == 0) {
+ say "got ace where not expecting";
+ return 1;
+ }
+
+ $f->del_remote(1);
+
+ return 0;
+}
+
+# test smbcacls '--add' attempts to add the ACL for the file
+# (note: first part of the test 'set' ACL to (F) then attempts to add new ACL
+# before:
+#
+# +-tar_test_dir/
+# +-file.1 (F)
+#
+# after/expected:
+#
+# +-tar_test_dir/
+# +-file.1 (F,READ)
+#
+sub test_simple_single_add
+{
+ my @files;
+ my $acl_str = "ACL:$USER:ALLOWED/0x0/FULL";
+ my $dny_acl_str = "ACL:$USER:DENIED/0x0/READ";
+ my $ace;
+ my $ret;
+ my $out;
+
+ my $f = File->new_local("$TMP/file-1");
+ $f->put_remote("file-1");
+
+ ($out, $ret) = smb_cacls('--set', $acl_str, $f->smb_remotepath);
+ if ($ret != 0 ) {
+ return 1;
+ }
+ # only a single ACE string in the ACL
+ $ace = ace_parse_str($acl_str);
+ $ret = file_ace_check($f->smb_remotepath, $ace);
+ if ($ret != 0) {
+ say "missing ace";
+ return 1;
+ }
+
+ ($out, $ret) = smb_cacls('--add', $dny_acl_str, $f->smb_remotepath);
+ if ($ret != 0 ) {
+ return 1;
+ }
+ $ace = ace_parse_str($dny_acl_str);
+ $ret = file_ace_check($f->smb_remotepath, $ace);
+ if ($ret != 0) {
+ say "missing ace";
+ return 1;
+ }
+
+ $f->del_remote(1);
+
+ return 0;
+}
+
+
+# test smbcacls '--propagate-inheritance --add' which attempts to add the ACL
+# for the file and additionally use inheritance rules to propagate appropriate
+# changes to children
+#
+# This test adds an ACL with (OI)(READ)
+#
+# before:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(I)(F)
+# | +-file.1 (I)(F)
+# | +-nested/ (OI)(CI)(I)(F)
+# | +-file.2 (I)(F)
+#
+# after/expected:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(I)(F), (OI)(READ)
+# | +-file.1 (I)(F), (I)(READ)
+# | +-nested/ (OI)(CI)(I)(F), (OI)(IO)(I)(READ)
+# | +-file.2 (I)(F), (I)(READ)
+#
+
+sub test_simple_oi_add
+{
+ my @files;
+ my $dir_add_acl_str = "ACL:$USER:ALLOWED/OI/READ";
+ my $obj_inherited_ace_str = "ACL:$USER:ALLOWED/I/READ";
+ my $dir_inherited_ace_str = "ACL:$USER:ALLOWED/OI|IO|I/READ";
+ my $dir_ace;
+ my $child_file_ace;
+ my $child_dir_ace;
+ my $ret;
+ my $out;
+
+ my $f1 = File->new_local("$TMP/file-1");
+ $f1->put_remote("oi_dir/file-1");
+ my $f2 = File->new_local("$TMP/file-2");
+ $f2->put_remote("oi_dir/nested/file-2");
+
+ ($out, $ret) = smb_cacls('--propagate-inheritance', '--add',
+ $dir_add_acl_str, $f1->smb_remotedir);
+ if ($ret != 0 ) {
+ return 1;
+ }
+
+ # check top level container 'oi_dir' has OI/READ
+ $dir_ace = ace_parse_str($dir_add_acl_str);
+ $ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+ if ($ret != 0) {
+ say "missing ace";
+ return 1;
+ }
+ # file 'oi_dir/file-1' should have inherited I/READ
+ $child_file_ace = ace_parse_str($obj_inherited_ace_str);
+ $ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+
+ if ($ret != 0) {
+ say "missing ace";
+ return 1;
+ }
+
+ # nested dir 'oi_dir/nested/' should have OI|IO/READ
+ $child_dir_ace = ace_parse_str($dir_inherited_ace_str);
+ $ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+ if ($ret != 0) {
+ say "missing ace";
+ return 1;
+ }
+
+ # nested file 'oi_dir/nested/file-2' should have inherited I/READ
+ $ret = file_ace_check($f2->smb_remotepath, $child_file_ace);
+ if ($ret != 0) {
+ say "got ace where not expecting";
+ return 1;
+ }
+
+ $f1->del_remote();
+ $f2->del_remote(1);
+
+ return 0;
+}
+# test smbcacls '--propagate-inheritance --add' which attempts to add the ACL
+# for the file and additionally use inheritance rules to propagate appropriate
+# changes to children
+#
+# This test adds an ACL with (OI)(READ)
+#
+# before:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(I)(F), (OI)(IO)(READ)
+# | +-file.1 (I)(F), (I)(READ)
+# | +-nested/ (OI)(CI)(I)(F), (OI)(IO)(I)(READ)
+# | +-file.2 (I)(F), (I)(READ)
+#
+# after/expected:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(I)(F)
+# | +-file.1 (I)(F)
+# | +-nested/ (OI)(CI)(I)(F)
+# | +-file.2 (I)(F)
+#
+sub test_simple_oi_delete
+{
+ my @files;
+ my $dir_acl_str = "ACL:$USER:ALLOWED/OI/READ";
+ my $obj_inherited_ace_str = "ACL:$USER:ALLOWED/I/READ";
+ my $dir_inherited_ace_str = "ACL:$USER:ALLOWED/OI|IO|I/READ";
+ my $dir_ace;
+ my $child_file_ace;
+ my $child_dir_ace;
+ my $ret;
+ my $out;
+
+ my $f1 = File->new_local("$TMP/file-1");
+ $f1->put_remote("oi_dir/file-1");
+ my $f2 = File->new_local("$TMP/file-2");
+ $f2->put_remote("oi_dir/nested/file-2");
+
+ # add flags on oi_dir
+ ($out, $ret) = smb_cacls('--add', $dir_acl_str, $f1->smb_remotedir);
+ if ($ret != 0 ) {
+ return 1;
+ }
+ # add flags on oi_dir/nested
+ ($out, $ret) = smb_cacls('--add', $dir_inherited_ace_str,
+ $f2->smb_remotedir);
+ if ($ret != 0 ) {
+ return 1;
+ }
+ # add flags on oi_dir/file-1
+ ($out, $ret) = smb_cacls('--add', $obj_inherited_ace_str,
+ $f1->smb_remotepath);
+ if ($ret != 0 ) {
+ return 1;
+ }
+ # add flags on oi_dir/nested/file-2
+ ($out, $ret) = smb_cacls('--add', $obj_inherited_ace_str,
+ $f2->smb_remotepath);
+ if ($ret != 0 ) {
+ return 1;
+ }
+ ($out, $ret) = smb_cacls('--propagate-inheritance', '--delete',
+ $dir_acl_str,
+ $f1->smb_remotedir);
+ if ($ret != 0 ) {
+ return 1;
+ }
+
+ # check top level container 'oi_dir' no longer has OI/READ
+ $dir_ace = ace_parse_str($dir_acl_str);
+ $ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+ if ($ret == 0) {
+ say "unexpected ace";
+ return 1;
+ }
+ # file 'oi_dir/file-1' should no longer have inherited I/READ
+ $child_file_ace = ace_parse_str($obj_inherited_ace_str);
+ $ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+
+ if ($ret == 0) {
+ say "unexpected ace";
+ return 1;
+ }
+
+ # nested dir 'oi_dir/nested/' should no longer have OI|IO/READ
+ $child_dir_ace = ace_parse_str($dir_inherited_ace_str);
+ $ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+ if ($ret == 0) {
+ say "unexpected ace";
+ return 1;
+ }
+
+ # nested file 'oi_dir/nested/file-2' should no longer have inherited I/READ
+ $ret = file_ace_check($f2->smb_remotepath, $child_file_ace);
+ if ($ret == 0) {
+ say "got ace where not expecting";
+ return 1;
+ }
+ $f1->del_remote();
+ $f2->del_remote(1);
+
+ return 0;
+}
+
+# test smbcacls '--propagate-inheritance --add' which attempts to add the ACL
+# for the file and additionally use inheritance rules to propagate appropriate
+# changes to children
+#
+# This test adds an ACL with (CI)(READ)
+#
+# before:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(I)(F)
+# | +-file.1 (I)(F)
+# | +-nested/ (OI)(CI)(I)(F)
+# | +-file.2 (I)(F)
+#
+# after/expected:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(I)(F), (CI)(READ)
+# | +-file.1 (I)(F)
+# | +-nested/ (OI)(CI)(I)(F), (CI)((I)(READ)
+# | +-file.2 (I)(F)
+sub test_simple_ci_add
+{
+ my @files;
+ my $dir_add_acl_str = "ACL:$USER:ALLOWED/CI/READ";
+ my $file_inherited_ace_str = "ACL:$USER:ALLOWED/I/READ";
+ my $dir_inherited_ace_str = "ACL:$USER:ALLOWED/CI|I/READ";
+ my $dir_ace;
+ my $child_file_ace;
+ my $child_dir_ace;
+ my $ret;
+ my $out;
+
+ my $f1 = File->new_local("$TMP/file-1");
+ $f1->put_remote("oi_dir/file-1");
+ my $f2 = File->new_local("$TMP/file-2");
+ $f2->put_remote("oi_dir/nested/file-2");
+ ($out, $ret) = smb_cacls('--propagate-inheritance', '--add',
+ $dir_add_acl_str, $f1->smb_remotedir);
+ if ($ret != 0 ) {
+ return 1;
+ }
+ # check top level container 'oi_dir' has CI/READ
+ $dir_ace = ace_parse_str($dir_add_acl_str);
+ $ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+ if ($ret != 0) {
+ say "missing ace";
+ return 1;
+ }
+
+
+ $child_file_ace = ace_parse_str($file_inherited_ace_str);
+ # nested file 'oi_dir/file-1' should NOT have inherited I/READ
+ $ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+ if ($ret == 0) {
+ say "got ace where not expecting";
+ return 1;
+ }
+
+ # nested dir 'oi_dir/nested/' should have CI|I|READ
+ $child_dir_ace = ace_parse_str($dir_inherited_ace_str);
+ $ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+ if ($ret != 0) {
+ return 1;
+ }
+
+ $f1->del_remote();
+ $f2->del_remote(1);
+
+ return 0;
+}
+
+# test smbcacls '--propagate-inheritance --add' which attempts to add the ACL
+# for the file and additionally use inheritance rules to propagate appropriate
+# changes to children
+#
+# This test delete an ACL with (CI)(READ)
+#
+# before:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(I)(F), (CI)(READ)
+# | +-file.1 (I)(F)
+# | +-nested/ (OI)(CI)(I)(F), (CI)((I)(READ)
+# | +-file.2 (I)(F)
+#
+# after/expected:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(I)(F)
+# | +-file.1 (I)(F)
+# | +-nested/ (OI)(CI)(I)(F)
+# | +-file.2 (I)(F)
+sub test_simple_ci_delete
+{
+ my @files;
+ my $dir_acl_str = "ACL:$USER:ALLOWED/CI/READ";
+ my $file_inherited_ace_str = "ACL:$USER:ALLOWED/I/READ";
+ my $dir_inherited_ace_str = "ACL:$USER:ALLOWED/CI|I/READ";
+ my $dir_ace;
+ my $child_file_ace;
+ my $child_dir_ace;
+ my $ret;
+ my $out;
+
+ my $f1 = File->new_local("$TMP/file-1");
+ $f1->put_remote("oi_dir/file-1");
+ my $f2 = File->new_local("$TMP/file-2");
+ $f2->put_remote("oi_dir/nested/file-2");
+
+ # add flags on oi_dir
+ ($out, $ret) = smb_cacls('--add', $dir_acl_str, $f1->smb_remotedir);
+ if ($ret != 0 ) {
+ return 1;
+ }
+ # add flags on oi_dir/nested
+ ($out, $ret) = smb_cacls('--add', $dir_inherited_ace_str,
+ $f2->smb_remotedir);
+ if ($ret != 0 ) {
+ return 1;
+ }
+ # make sure no (I|RX) flags on oi_dir/file-1
+ ($out, $ret) = smb_cacls('--delete', $file_inherited_ace_str,
+ $f1->smb_remotepath);
+ if ($ret != 0 ) {
+ return 1;
+ }
+ # make sure no (I|RX) flags on oi_dir/nested/file-2
+ ($out, $ret) = smb_cacls('--delete', $file_inherited_ace_str,
+ $f2->smb_remotepath);
+ if ($ret != 0 ) {
+ return 1;
+ }
+ ($out, $ret) = smb_cacls('--propagate-inheritance', '--delete',
+ $dir_acl_str, $f1->smb_remotedir);
+
+ if ($ret != 0 ) {
+ return 1;
+ }
+ # check top level container 'oi_dir' no longer has CI/READ
+ $dir_ace = ace_parse_str($dir_acl_str);
+ $ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+
+ if ($ret == 0) {
+ say "unexpectex ace";
+ return 1;
+ }
+
+ $child_file_ace = ace_parse_str($file_inherited_ace_str);
+ # nested file 'oi_dir/file-1' should NOT have inherited I/READ
+ $ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+ if ($ret == 0) {
+ say "got ace where not expecting";
+ return 1;
+ }
+
+ # nested dir 'oi_dir/nested/' should no longer have CI|I|READ
+ $child_dir_ace = ace_parse_str($dir_inherited_ace_str);
+ $ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+ if ($ret == 0) {
+ say "unexpected ace";
+ return 1;
+ }
+
+ $f1->del_remote();
+ $f2->del_remote(1);
+
+ return 0;
+}
+
+# test smbcacls '--propagate-inheritance --add' which attempts to add the ACL
+# for the file and additionally use inheritance rules to propagate appropriate
+# changes to children
+#
+# This test adds an ACL with (CI)(OI)(READ)
+#
+# before:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(I)(F)
+# | +-file.1 (I)(F)
+# | +-nested/ (OI)(CI)(I)(F)
+# | +-file.2 (I)(F)
+#
+# after/expected:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(I)(F), (CI)(OI)READ)
+# | +-file.1 (I)(F), (I)(READ)
+# | +-nested/ (OI)(CI)(I)(F), (CI)(OI)(I)(READ)
+# | +-file.2 (I)(F), (I)(READ)
+sub test_simple_cioi_add
+{
+ my @files;
+ my $dir_add_acl_str = "ACL:$USER:ALLOWED/OI|CI/READ";
+ my $file_inherited_ace_str = "ACL:$USER:ALLOWED/I/READ";
+ my $dir_inherited_ace_str = "ACL:$USER:ALLOWED/OI|CI|I/READ";
+ my $dir_ace;
+ my $child_file_ace;
+ my $child_dir_ace;
+ my $ret;
+ my $out;
+
+ my $f1 = File->new_local("$TMP/file-1");
+ $f1->put_remote("oi_dir/file-1");
+ my $f2 = File->new_local("$TMP/file-2");
+ $f2->put_remote("oi_dir/nested/file-2");
+
+ ($out, $ret) = smb_cacls('--propagate-inheritance', '--add',
+ $dir_add_acl_str, $f1->smb_remotedir);
+ if ($ret != 0 ) {
+ return 1;
+ }
+ # check top level container 'oi_dir' has OI|CI/READ
+ $dir_ace = ace_parse_str($dir_add_acl_str);
+ $ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+ if ($ret != 0) {
+ say "missing expected ace";
+ return 1;
+ }
+
+
+ $child_file_ace = ace_parse_str($file_inherited_ace_str);
+ # nested file 'oi_dir/file-1' should have inherited I/READ
+ $ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+ if ($ret != 0) {
+ say "missing expected ace";
+ return 1;
+ }
+
+ # nested dir 'oi_dir/nested/' should have OI|CI|I|READ
+ $child_dir_ace = ace_parse_str($dir_inherited_ace_str);
+ $ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+ if ($ret != 0) {
+ say "missing expected ace";
+ return 1;
+ }
+
+ $f1->del_remote();
+ $f2->del_remote(1);
+
+ return 0;
+}
+
+# test smbcacls '--propagate-inheritance --delete' which attempts to delete the
+# ACL for the file and additionally use inheritance rules to propagate
+# appropriate changes to children
+#
+# This test deletes an ACL with (CI)(OI)(READ)
+#
+# before:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(I)(F), (CI)(OI)(READ)
+# | +-file.1 (I)(F), (I)(READ)
+# | +-nested/ (OI)(CI)(I)(F), (CI)(OI)(I)(READ)
+# | +-file.2 (I)(F), (I)(READ)
+#
+# after/expected:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(I)(F)
+# | +-file.1 (I)(F)
+# | +-nested/ (OI)(CI)(I)(F)
+# | +-file.2 (I)(F)
+#
+
+sub test_simple_cioi_delete
+{
+ my @files;
+ my $dir_acl_str = "ACL:$USER:ALLOWED/OI|CI/READ";
+ my $file_inherited_ace_str = "ACL:$USER:ALLOWED/I/READ";
+ my $dir_inherited_ace_str = "ACL:$USER:ALLOWED/OI|CI|I/READ";
+ my $dir_ace;
+ my $child_file_ace;
+ my $child_dir_ace;
+ my $ret;
+ my $out;
+
+ my $f1 = File->new_local("$TMP/file-1");
+ $f1->put_remote("oi_dir/file-1");
+ my $f2 = File->new_local("$TMP/file-2");
+ $f2->put_remote("oi_dir/nested/file-2");
+
+ # add flags on oi_dir
+ ($out, $ret) = smb_cacls('--add', $dir_acl_str, $f1->smb_remotedir);
+ if ($ret != 0) {
+ return 1;
+ }
+ # add flags on oi_dir/nested
+ ($out, $ret) = smb_cacls('--add', $dir_inherited_ace_str,
+ $f2->smb_remotedir);
+ if ($ret != 0) {
+ return 1;
+ }
+ # add flags on oi_dir/file-1
+ ($out, $ret) = smb_cacls('--add', $file_inherited_ace_str,
+ $f1->smb_remotepath);
+ if ($ret != 0) {
+ return 1;
+ }
+ # add flags on oi_dir/nested/file-2
+ ($out, $ret) = smb_cacls('--add', $file_inherited_ace_str,
+ $f2->smb_remotepath);
+ if ($ret != 0) {
+ return 1;
+ }
+
+ ($out, $ret) = smb_cacls('--propagate-inheritance', '--delete',
+ $dir_acl_str,
+ $f1->smb_remotedir);
+ if ($ret != 0) {
+ return 1;
+ }
+
+ # check top level container 'oi_dir' no longer has OI|CI/READ
+ $dir_ace = ace_parse_str($dir_acl_str);
+ $ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+
+ if ($ret == 0) {
+ say "unexpected ace";
+ return 1;
+ }
+
+ $child_file_ace = ace_parse_str($file_inherited_ace_str);
+ # nested file 'oi_dir/file-1' should NOT have inherited I/READ
+ $ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+ if ($ret == 0) {
+ say "unexpected ace";
+ return 1;
+ }
+
+ # nested dir 'oi_dir/nested/' should no longer have OI|CI|I|READ
+ $child_dir_ace = ace_parse_str($dir_inherited_ace_str);
+ $ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+ if ($ret == 0) {
+ return 1;
+ }
+
+ $f1->del_remote();
+ $f2->del_remote(1);
+
+ return 0;
+}
+
+# test smbcacls '--propagate-inheritance --modify' which attempts to modify the
+# ACLfor the file and additionally use inheritance rules to propagate
+# appropriate changes to children
+#
+# This test first adds an ACL with (CI)(OI)(R), then it modifies that acl to be
+# (CI)(OI)(D) - where D == 0x00110000
+#
+# before:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (CI)(OI)(R)
+# | +-file.1 (I)(R)
+# | +-nested/ (CI)(OI)(I)(R)
+# | +-file.2 (I)(R)
+#
+# after/expected:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (CI)(OI)(CHANGE)
+# | +-file.1 (I)(CHANGE)
+# | +-nested/ (CI)(OI)(I)(CHANGE)
+# | +-file.2 (I)(CHANGE)
+
+sub test_simple_cioi_modify
+{
+ my @files;
+ my $dir_acl_str = "ACL:$USER:ALLOWED/OI|CI/R";
+ my $file_inherited_ace_str = "ACL:$USER:ALLOWED/I/R";
+ my $dir_inherited_ace_str = "ACL:$USER:ALLOWED/OI|CI|I/R";
+
+ my $dir_mod_acl_str = "ACL:$USER:ALLOWED/OI|CI/CHANGE";
+ my $file_mod_inherited_ace_str = "ACL:$USER:ALLOWED/I/CHANGE";
+ my $dir_mod_inherited_ace_str = "ACL:$USER:ALLOWED/OI|CI|I/CHANGE";
+
+ my $dir_ace;
+ my $child_file_ace;
+ my $child_dir_ace;
+ my $ret;
+ my $out;
+
+ my $f1 = File->new_local("$TMP/file-1");
+ $f1->put_remote("oi_dir/file-1");
+ my $f2 = File->new_local("$TMP/file-2");
+ $f2->put_remote("oi_dir/nested/file-2");
+
+ # add flags on oi_dir
+
+ # This is somwhat artificial, we need to add a new acl to the directory
+ # so that the following modify operation doesn't fail. Previously
+ # '--modify' was used in place of '--add' but that resulted in failure
+ # to access the directory ( or even modify the acl ).
+ # Note: when running this test against a windows server it seems that
+ # running as Administrator ensures best results
+
+ ($out, $ret) = smb_cacls('--add', $dir_acl_str, $f1->smb_remotedir);
+ if ($ret != 0) {
+ return 1;
+ }
+ # add flags on oi_dir/nested
+ ($out, $ret) = smb_cacls('--add', $dir_inherited_ace_str,
+ $f2->smb_remotedir);
+ if ($ret != 0) {
+ return 1;
+ }
+ # add flags on oi_dir/file-1
+ ($out, $ret) = smb_cacls('--add', $file_inherited_ace_str,
+ $f1->smb_remotepath);
+ if ($ret != 0) {
+ return 1;
+ }
+ # add flags on oi_dir/nested/file-2
+ ($out, $ret) = smb_cacls('--add', $file_inherited_ace_str,
+ $f2->smb_remotepath);
+ if ($ret != 0) {
+ return 1;
+ }
+
+ ($out, $ret) = smb_cacls('--propagate-inheritance', '--modify',
+ $dir_mod_acl_str,
+ $f1->smb_remotedir);
+ if ($ret != 0) {
+ return 1;
+ }
+
+ # check top level container 'oi_dir' has OI|CI/CHANGE
+ $dir_ace = ace_parse_str($dir_mod_acl_str);
+ $ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+ if ($ret != 0) {
+ return 1;
+ }
+
+ $child_file_ace = ace_parse_str($file_mod_inherited_ace_str);
+ # nested file 'oi_dir/file-1' should have inherited I|CHANGE
+ $ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+ if ($ret != 0) {
+ say "missing expected ace";
+ return 1;
+ }
+
+ # nested dir 'oi_dir/nested/' should have OI|CI|I|CHANGE
+ $child_dir_ace = ace_parse_str($dir_mod_inherited_ace_str);
+ $ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+ if ($ret != 0) {
+ return 1;
+ }
+
+ $f1->del_remote();
+ $f2->del_remote(1);
+
+ return 0;
+}
+# test smbcacls '--propagate-inheritance --modify' which attempts to modify ACL
+# for the file and additionally use inheritance rules to propagate appropriate
+# changes to children
+#
+# This test first adds an ACL with (OI)(R), then it modifies that acl to be
+# (OI)(D) - where D == 0x00110000
+#
+# before:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(IO)(R)
+# | +-file.1 (I)(R)
+# | +-nested/ (OI)(IO)(I)(R)
+# | +-file.2 (I)(R)
+#
+# after/expected:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(IO)(CHANGE)
+# | +-file.1 (I)(CHANGED)
+# | +-nested/ (OI)(IO)(I)(CHANGED)
+# | +-file.2 (I)(CHANGED)
+sub test_simple_oi_modify
+{
+ my @files;
+ my $explict_access_ace_str = "ACL:$USER:ALLOWED/0x0/RWD";
+
+
+ my $dir_mod_acl_str = "ACL:$USER:ALLOWED/OI/CHANGE";
+ my $file_mod_inherited_ace_str = "ACL:$USER:ALLOWED/I/CHANGE";
+ my $dir_mod_inherited_ace_str = "ACL:$USER:ALLOWED/OI|IO|I/CHANGE";
+
+ my $dir_ace;
+ my $child_file_ace;
+ my $child_dir_ace;
+ my $ret;
+ my $out;
+
+ my $f1 = File->new_local("$TMP/file-1");
+ $f1->put_remote("oi_dir/file-1");
+ my $f2 = File->new_local("$TMP/file-2");
+ $f2->put_remote("oi_dir/nested/file-2");
+
+ # add flags on oi_dir
+
+ # This is somwhat artificial, we need to add a new acl to the directory
+ # so that the following modify operation doesn't fail. Previously
+ # '--modify' was used in place of '--add' but that resulted in failure
+ # to access the directory ( or even modify the acl ).
+ # Note: when running this test against a windows server it seems that
+ # running as Administrator ensures best results
+
+ ($out, $ret) = smb_cacls('--add', $explict_access_ace_str,
+ $f1->smb_remotedir);
+ if ($ret != 0) {
+ return 1;
+ }
+ # add flags on oi_dir/nested
+ ($out, $ret) = smb_cacls('--add', $explict_access_ace_str,
+ $f2->smb_remotedir);
+ if ($ret != 0) {
+ return 1;
+ }
+ # add flags on oi_dir/file-1
+ ($out, $ret) = smb_cacls('--add', $explict_access_ace_str,
+ $f1->smb_remotepath);
+ if ($ret != 0) {
+ return 1;
+ }
+ # add flags on oi_dir/nested/file-2
+ ($out, $ret) = smb_cacls('--add', $explict_access_ace_str,
+ $f2->smb_remotepath);
+ if ($ret != 0) {
+ return 1;
+ }
+ ($out, $ret) = smb_cacls('--propagate-inheritance', '--modify',
+ $dir_mod_acl_str, $f1->smb_remotedir);
+ if ($ret != 0) {
+ return 1;
+ }
+
+ # check top level container 'oi_dir' has OI/CHANGE
+ $dir_ace = ace_parse_str($dir_mod_acl_str);
+ $ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+ if ($ret != 0) {
+ return 1;
+ }
+ # file 'oi_dir/file-1' should have inherited I/CHANGE
+ $child_file_ace = ace_parse_str($file_mod_inherited_ace_str);
+ $ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+
+ if ($ret != 0) {
+ return 1;
+ }
+
+ # nested dir 'oi_dir/nested/' should have OI|IO/CHANGE
+ $child_dir_ace = ace_parse_str($dir_mod_inherited_ace_str);
+ $ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+ if ($ret != 0) {
+ return 1;
+ }
+
+ # nested file 'oi_dir/nested/file-2' should have inherited I/CHANGE
+ $ret = file_ace_check($f2->smb_remotepath, $child_file_ace);
+ if ($ret != 0) {
+ say "got ace where not expecting";
+ return 1;
+ }
+
+ $f1->del_remote();
+ $f2->del_remote(1);
+
+ return 0;
+}
+# test smbcacls '--propagate-inheritance --modify' which attempts to modify ACL
+# for the file and additionally use inheritance rules to propagate appropriate
+# changes to children
+#
+# This test first adds an ACL with (CI)(R), then it modifies that acl to be
+# (CI)(D) - where D == 0x00110000
+#
+# before:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (CI)(R)
+# | +-file.1 (I)(F)
+# | +-nested/ (CI)(I)(R)
+# | +-file.2 (I)(F)
+#
+# after/expected:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (CI)(CHANGE)
+# | +-file.1 (I)(F)
+# | +-nested/ (CI)(I)(CHANGE)
+# | +-file.2 (I)(F)
+
+sub test_simple_ci_modify
+{
+ my @files;
+ my $dir_acl_str = "ACL:$USER:ALLOWED/CI/READ";
+ my $file_inherited_ace_str = "ACL:$USER:ALLOWED/I/READ";
+ my $dir_inherited_ace_str = "ACL:$USER:ALLOWED/CI|I/READ";
+
+ my $dir_mod_acl_str = "ACL:$USER:ALLOWED/CI/CHANGE";
+ my $file_mod_inherited_ace_str = "ACL:$USER:ALLOWED/I/CHANGE";
+ my $dir_mod_inherited_ace_str = "ACL:$USER:ALLOWED/CI|I/CHANGE";
+ my $delete_ace_str = "ACL:$USER:ALLOWED/0x0/RWD";
+
+ my $dir_ace;
+ my $child_file_ace;
+ my $child_dir_ace;
+ my $ret;
+ my $out;
+
+ my $f1 = File->new_local("$TMP/file-1");
+ $f1->put_remote("oi_dir/file-1");
+ my $f2 = File->new_local("$TMP/file-2");
+ $f2->put_remote("oi_dir/nested/file-2");
+
+ # add flags on oi_dir
+
+ # This is somwhat artificial, we need to add a new acl to the directory
+ # so that the following modify operation doesn't fail. Previously
+ # '--modify' was used in place of '--add' but that resulted in failure
+ # to access the directory ( or even modify the acl ).
+ # Note: when running this test against a windows server it seems that
+ # running as Administrator ensures best results
+
+ ($out, $ret) = smb_cacls('--add', $dir_acl_str, $f1->smb_remotedir);
+ if ($ret != 0) {
+ return 1;
+ }
+ # add flags on oi_dir/nested
+ ($out, $ret) = smb_cacls('--add', $dir_inherited_ace_str,
+ $f2->smb_remotedir);
+ if ($ret != 0) {
+ return 1;
+ }
+
+ ($out, $ret) = smb_cacls('--propagate-inheritance', '--modify',
+ $dir_mod_acl_str, $f1->smb_remotedir);
+ if ($ret != 0) {
+ return 1;
+ }
+
+ # check top level container 'oi_dir' has CI/CHANGE
+ $dir_ace = ace_parse_str($dir_mod_acl_str);
+ $ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+ if ($ret != 0) {
+ return 1;
+ }
+
+
+ $child_file_ace = ace_parse_str($file_mod_inherited_ace_str);
+ # nested file 'oi_dir/file-1' should NOT have inherited I/CHANGE
+ $ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+ if ($ret == 0) {
+ say "got ace where not expecting";
+ return 1;
+ }
+
+ # nested dir 'oi_dir/nested/' should have OI|I/CHANGE
+ $child_dir_ace = ace_parse_str($dir_mod_inherited_ace_str);
+ $ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+ if ($ret != 0) {
+ return 1;
+ }
+
+ # set some flags to allow us to delete the files
+ smb_cacls('--set', $delete_ace_str, $f1->smb_remotepath);
+ smb_cacls('--set', $delete_ace_str, $f2->smb_remotepath);
+ $f1->del_remote();
+ $f2->del_remote(1);
+
+ return 0;
+}
+
+# test smbcacls '--propagate-inheritance --set' which attempts to set the ACL
+# for the file and additionally use inheritance rules to propagate appropriate
+# changes to children
+#
+# This test adds an ACL with (CI)(OI)(READ)
+#
+# before:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(I)(F)
+# | +-file.1 (I)(F)
+# | +-nested/ (OI)(CI)(I)(F)
+# | +-file.2 (I)(F)
+#
+# after/expected:
+# fail, oid_dir has inheritance enabled, set should fail and exit with '1'
+
+sub test_simple_set_fail()
+{
+ my @files;
+ my $dir_acl_str = "ACL:$USER:ALLOWED/OI|CI/R";
+ my $file_inherited_ace_str = "ACL:$USER:ALLOWED/I/R";
+ my $dir_inherited_ace_str = "ACL:$USER:ALLOWED/OI|CI|I/R";
+ my $dir_ace;
+ my $child_file_ace;
+ my $child_dir_ace;
+ my $ret;
+ my $out;
+
+ my $f1 = File->new_local("$TMP/file-1");
+ $f1->put_remote("oi_dir/file-1");
+ my $f2 = File->new_local("$TMP/file-2");
+ $f2->put_remote("oi_dir/nested/file-2");
+
+ ($out, $ret) = smb_cacls('--propagate-inheritance', '--set',
+ $dir_acl_str, $f1->smb_remotedir);
+
+ if ($ret == 0 ) {
+ say "smb_cacls '--set' succeeded unexpectedly while processing container with inheritance enabled";
+ return 1;
+ }
+ $f1->del_remote();
+ $f2->del_remote(1);
+ return 0;
+}
+
+# test smbcacls '--propagate-inheritance --set' which attempts to set the ACL
+# for the file and additionally use inheritance rules to propagate appropriate
+# changes to children
+#
+# This test adds an ACL with (CI)(OI)(RWD) additionally it removes
+# inheritance from oi_dir
+#
+# before:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(I)(F)
+# | +-file.1 (I)(F)
+# | +-nested/ (OI)(CI)(I)(F)
+# | +-file.2 (I)(F)
+#
+# after/expected:
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(RWD)
+# | +-file.1 (I)(RWD)
+# | +-nested/ (OI)(CI)(I)(RWD)
+# | +-file.2 (I)(RWD)
+#
+
+sub test_simple_oici_set()
+{
+ my @files;
+ my $dir_acl_str = "ACL:$USER:ALLOWED/OI|CI/RWD";
+ my $file_inherited_ace_str = "ACL:$USER:ALLOWED/I/RWD";
+ my $dir_inherited_ace_str = "ACL:$USER:ALLOWED/OI|CI|I/RWD";
+ my $dir_ace;
+ my $child_file_ace;
+ my $child_dir_ace;
+ my $ret;
+ my $out;
+
+ my $f1 = File->new_local("$TMP/file-1");
+ $f1->put_remote("oi_dir/file-1");
+ my $f2 = File->new_local("$TMP/file-2");
+ $f2->put_remote("oi_dir/nested/file-2");
+
+ ($out, $ret) = smb_cacls('--inherit=copy', $f1->smb_remotedir);
+
+ # smb_cacls --inherit=remove
+ if ($ret != 0 ) {
+ say "failed to remove inheritance enabled";
+ return 1;
+ }
+
+ ($out, $ret) = smb_cacls('--propagate-inheritance', '--set',
+ $dir_acl_str, $f1->smb_remotedir);
+ if ($ret != 0 ) {
+ return 1;
+ }
+ # check top level container 'oi_dir' has OI|CI/RWD
+ $dir_ace = ace_parse_str($dir_acl_str);
+ $ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+ if ($ret != 0) {
+ return 1;
+ }
+ # check nested file has I/RW
+ $child_file_ace = ace_parse_str($file_inherited_ace_str);
+ $ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+ if ($ret != 0) {
+ return 1;
+ }
+
+ # check nested dir has OI|CI|I/RW
+ $child_dir_ace = ace_parse_str($dir_inherited_ace_str);
+ $ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+ if ($ret != 0) {
+ return 1;
+ }
+ $f1->del_remote();
+ $f2->del_remote(1);
+ return 0;
+}
+
+# test smbcacls '--propagate-inheritance --set' which attempts to set the ACL
+# for the file and additionally use inheritance rules to propagate appropriate
+# changes to children
+#
+# This test adds an ACL with (CI)(RWD) additionally it removes
+# inheritance from oi_dir
+#
+# before:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(I)(F)
+# | +-file.1 (I)(F)
+# | +-nested/ (OI)(CI)(I)(F)
+# | +-file.2 (I)(F)
+#
+# after/expected:
+# +-tar_test_dir/ (01)(CI)(I)(RWD)
+# +-oi_dir/ (CI)(RWD)
+# | +-file.1
+# | +-nested/ (CI)(I)(RWD)
+# | +-file.2
+#
+
+sub test_simple_ci_set()
+{
+ my @files;
+ my $dir_acl_str = "ACL:$USER:ALLOWED/CI/RWD";
+ my $file_inherited_ace_str = "ACL:$USER:ALLOWED/I/RWD";
+ my $dir_inherited_ace_str = "ACL:$USER:ALLOWED/CI|I/RWD";
+ my $delete_ace_str = "ACL:$USER:ALLOWED/0x0/RWD";
+ my $dir_ace;
+ my $child_file_ace;
+ my $child_dir_ace;
+ my $ret;
+ my $out;
+
+ my $f1 = File->new_local("$TMP/file-1");
+ $f1->put_remote("oi_dir/file-1");
+ my $f2 = File->new_local("$TMP/file-2");
+ $f2->put_remote("oi_dir/nested/file-2");
+
+ ($out, $ret) = smb_cacls('--inherit=copy', $f1->smb_remotedir);
+
+ # smb_cacls --inherit=remove
+ if ($ret != 0 ) {
+ say "failed to remove inheritance enabled";
+ return 1;
+ }
+ ($out, $ret) = smb_cacls('--propagate-inheritance', '--set',
+ $dir_acl_str, $f1->smb_remotedir);
+ if ($ret != 0) {
+ return 1;
+ }
+
+ my $nacl;
+ $nacl = num_acls_for_file($f1->smb_remotedir);
+
+ # Although there maybe a couple of users with associated acl(s)
+ # before set, after set there should only be 1 acl
+ if ($nacl != 1) {
+ say "unexpected number of acls ($nacl) after set";
+ return 1;
+ }
+
+ # check top level container 'oi_dir' has OI|CI/RWD
+ $dir_ace = ace_parse_str($dir_acl_str);
+ $ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+ if ($ret != 0) {
+ return 1;
+ }
+
+ # note can't check file because it has no ACL ( due to CI )
+
+ # check nested dir has OI|CI|I/RWD
+ $child_dir_ace = ace_parse_str($dir_inherited_ace_str);
+ $ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+ if ($ret != 0) {
+ return 1;
+ }
+ # set some flags to allow us to delete the files
+ smb_cacls('--set', $delete_ace_str, $f1->smb_remotepath);
+ smb_cacls('--set', $delete_ace_str, $f2->smb_remotepath);
+ $f1->del_remote();
+ $f2->del_remote(1);
+ return 0;
+}
+
+# test smbcacls '--propagate-inheritance --add' which attempts to add the ACL
+# for the file and additionally use inheritance rules to propagate appropriate
+# changes to children
+#
+# This test adds an ACL with (CI)(OI)(NP)(CHANGE)
+# (NP) - no propagation should not propagate the changes any further containers
+#
+# before:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(I)(F)
+# | +-file.1 (I)(F)
+# | +-nested/ (OI)(CI)(I)(F)
+# | +-file.2 (I)(F)
+#
+# after/expected:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(I)(F), (CI)(OI)(NP)(CHANGE)
+# | +-file.1 (I)(F), (I)(CHANGE)
+# | +-nested/ (OI)(CI)(I)(F), (I)(CHANGE)
+# | +-file.2 (I)(F)
+sub test_simple_cioinp_add
+{
+ my @files;
+ my $dir_add_acl_str = "ACL:$USER:ALLOWED/OI|CI|NP/CHANGE";
+ my $inherited_ace_str = "ACL:$USER:ALLOWED/I/CHANGE";
+ my $dir_ace;
+ my $child_file_ace;
+ my $child_dir_ace;
+ my $ret;
+ my $out;
+
+ my $f1 = File->new_local("$TMP/file-1");
+ $f1->put_remote("oi_dir/file-1");
+ my $f2 = File->new_local("$TMP/file-2");
+ $f2->put_remote("oi_dir/nested/file-2");
+
+ ($out, $ret) = smb_cacls('--propagate-inheritance', '--add',
+ $dir_add_acl_str, $f1->smb_remotedir);
+ if ($ret != 0) {
+ return 1;
+ }
+
+ # check top level container 'oi_dir' has OI|CI|NP/READ
+ $dir_ace = ace_parse_str($dir_add_acl_str);
+ $ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+ if ($ret != 0) {
+ return 1;
+ }
+
+
+ $child_file_ace = ace_parse_str($inherited_ace_str);
+ # nested file 'oi_dir/file-1' should have inherited I/CHANGE
+ $ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+ if ($ret != 0) {
+ say "missing expected ace";
+ return 1;
+ }
+
+ # nested dir 'oi_dir/nested/file-2' should NOT have I/CHANGE
+ $child_dir_ace = ace_parse_str($inherited_ace_str);
+ $ret = file_ace_check($f2->smb_remotepath, $child_dir_ace);
+ if ($ret == 0) {
+ say "unexpected ace";
+ return 1;
+ }
+
+ $child_file_ace = ace_parse_str($inherited_ace_str);
+ # nested dir 'oi_dir/nested' should have inherited I/CHANGE
+ $ret = file_ace_check($f2->smb_remotedir, $child_file_ace);
+ if ($ret != 0) {
+ say "missing expected ace";
+ return 1;
+ }
+
+ $f1->del_remote();
+ $f2->del_remote(1);
+
+ return 0;
+}
+
+# test smbcacls '--propagate-inheritance --add' which attempts to add the ACL
+# for the file and additionally use inheritance rules to propagate appropriate
+# changes to children
+#
+# This test adds an ACL with (OI)(NP)(CHANGE)
+# (NP) - no propagation should not propagate the changes any further containers
+#
+# before:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(I)(F)
+# | +-file.1 (I)(F)
+# | +-nested/ (OI)(CI)(I)(F)
+# | +-file.2 (I)(F)
+#
+# after/expected:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(I)(F), (OI)(NP)(CHANGE)
+# | +-file.1 (I)(F), (I)(CHANGE)
+# | +-nested/ (OI)(CI)(I)(F)
+# | +-file.2 (I)(F)
+sub test_simple_oinp_add
+{
+ my @files;
+ my $dir_add_acl_str = "ACL:$USER:ALLOWED/OI|NP/CHANGE";
+ my $inherited_ace_str = "ACL:$USER:ALLOWED/I/CHANGE";
+ my $dir_ace;
+ my $child_file_ace;
+ my $child_dir_ace;
+ my $ret;
+ my $out;
+
+ my $f1 = File->new_local("$TMP/file-1");
+ $f1->put_remote("oi_dir/file-1");
+ my $f2 = File->new_local("$TMP/file-2");
+ $f2->put_remote("oi_dir/nested/file-2");
+
+ ($out, $ret) = smb_cacls('--propagate-inheritance', '--add',
+ $dir_add_acl_str, $f1->smb_remotedir);
+ if ($ret != 0) {
+ return 1;
+ }
+
+ # check top level container 'oi_dir' has OI|NP/READ
+ $dir_ace = ace_parse_str($dir_add_acl_str);
+ $ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+ if ($ret != 0) {
+ return 1;
+ }
+
+
+ $child_file_ace = ace_parse_str($inherited_ace_str);
+ # nested file 'oi_dir/file-1' should have inherited I/CHANGE
+ $ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+ if ($ret != 0) {
+ say "missing expected ace";
+ return 1;
+ }
+
+ # nested dir 'oi_dir/nested' should NOT have I/CHANGE
+ $child_dir_ace = ace_parse_str($inherited_ace_str);
+ $ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+ if ($ret != 0) {
+ say "unexpected ace";
+ return 1;
+ }
+
+ $child_file_ace = ace_parse_str($inherited_ace_str);
+ # nested file 'oi_dir/nested/file-1' should NOT have inherited I/CHANGE
+ $ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+ if ($ret != 0) {
+ say "unexpected ace";
+ return 1;
+ }
+
+ $f1->del_remote();
+ $f2->del_remote(1);
+
+ return 0;
+}
+
+# test smbcacls '--propagate-inheritance --add' which attempts to add the ACL
+# for the file and additionally use inheritance rules to propagate appropriate
+# changes to children
+#
+# This test adds an ACL with (CI)(NP)(CHANGE)
+# (NP) - no propagation should not propagate the changes any further containers
+#
+# before:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(I)(F)
+# | +-file.1 (I)(F)
+# | +-nested/ (OI)(CI)(I)(F)
+# | +-file.2 (I)(F)
+#
+# after/expected:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(I)(F), (CI)(NP)(CHANGE)
+# | +-file.1 (I)(F)
+# | +-nested/ (OI)(CI)(I)(F), (I)(CHANGE)
+# | +-file.2 (I)(F)
+sub test_simple_cinp_add
+{
+ my @files;
+ my $dir_add_acl_str = "ACL:$USER:ALLOWED/CI|NP/CHANGE";
+ my $inherited_ace_str = "ACL:$USER:ALLOWED/I/CHANGE";
+ my $dir_ace;
+ my $child_file_ace;
+ my $child_dir_ace;
+ my $ret;
+ my $out;
+
+ my $f1 = File->new_local("$TMP/file-1");
+ $f1->put_remote("oi_dir/file-1");
+ my $f2 = File->new_local("$TMP/file-2");
+ $f2->put_remote("oi_dir/nested/file-2");
+
+ ($out, $ret) = smb_cacls('--propagate-inheritance', '--add',
+ $dir_add_acl_str, $f1->smb_remotedir);
+ if ($ret != 0) {
+ return 1;
+ }
+
+ # check top level container 'oi_dir' has CI|NP/READ
+ $dir_ace = ace_parse_str($dir_add_acl_str);
+ $ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+ if ($ret != 0) {
+ return 1;
+ }
+
+
+ $child_file_ace = ace_parse_str($inherited_ace_str);
+ # nested file 'oi_dir/file-1' should NOT have inherited I/CHANGE
+ $ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+ if ($ret == 0) {
+ say "unexpected ace";
+ return 1;
+ }
+
+ # nested dir 'oi_dir/nested' should have I/CHANGE
+ $child_dir_ace = ace_parse_str($inherited_ace_str);
+ $ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+ if ($ret != 0) {
+ say "missing expected ace";
+ return 1;
+ }
+
+ $child_file_ace = ace_parse_str($inherited_ace_str);
+ # nested file 'oi_dir/nested/file-1' should NOT have inherited I/CHANGE
+ $ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+ if ($ret == 0) {
+ say "unexpected ace";
+ return 1;
+ }
+
+ $f1->del_remote();
+ $f2->del_remote(1);
+ return 0;
+}
+
+# test smbcacls '--propagate-inheritance --delete' which attempts to delete
+# the ACL for the file and additionally use inheritance rules to propagate
+# appropriate changes to children
+#
+# This test adds an ACL with (CI)(OI)(NP)(CHANGE)
+# (NP) - no propagation should not propagate the changes any further containers
+#
+# before:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(I)(F), (CI)(OI)(NP)(CHANGE)
+# | +-file.1 (I)(F), (I)(CHANGE)
+# | +-nested/ (OI)(CI)(I)(F), (I)(CHANGE)
+# | +-file.2 (I)(F)
+#
+# after/expected:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(I)(F)
+# | +-file.1 (I)(F)
+# | +-nested/ (OI)(CI)(I)(F)
+# | +-file.2 (I)(F)
+#
+sub test_simple_cioinp_delete
+{
+ my @files;
+ my $dir_add_acl_str = "ACL:$USER:ALLOWED/OI|CI|NP/CHANGE";
+ my $inherited_ace_str = "ACL:$USER:ALLOWED/I/CHANGE";
+ my $dir_ace;
+ my $child_file_ace;
+ my $child_dir_ace;
+ my $ret;
+ my $out;
+
+ my $f1 = File->new_local("$TMP/file-1");
+ $f1->put_remote("oi_dir/file-1");
+ my $f2 = File->new_local("$TMP/file-2");
+ $f2->put_remote("oi_dir/nested/file-2");
+
+ # set up 'before' permissions
+ ($out, $ret) = smb_cacls('--add', $dir_add_acl_str, $f1->smb_remotedir);
+ if ($ret != 0) {
+ return 1;
+ }
+ ($out, $ret) = smb_cacls('--add', $inherited_ace_str,
+ $f1->smb_remotepath);
+ if ($ret != 0) {
+ return 1;
+ }
+ ($out, $ret) = smb_cacls('--add', $inherited_ace_str,
+ $f2->smb_remotedir);
+ if ($ret != 0) {
+ return 1;
+ }
+
+ ($out, $ret) = smb_cacls('--propagate-inheritance', '--delete',
+ $dir_add_acl_str,
+ $f1->smb_remotedir);
+ if ($ret != 0) {
+ return 1;
+ }
+
+ # check top level container 'oi_dir' does NOT have OI|CI|NP/READ
+ $dir_ace = ace_parse_str($dir_add_acl_str);
+ $ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+ if ($ret == 0) {
+ say "unexpected ace";
+ return 1;
+ }
+
+
+ $child_file_ace = ace_parse_str($inherited_ace_str);
+ # nested file 'oi_dir/file-1' should NOT have inherited I/CHANGE
+ $ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+ if ($ret == 0) {
+ say "unexpected ace";
+ return 1;
+ }
+
+ $child_file_ace = ace_parse_str($inherited_ace_str);
+ # nested dir 'oi_dir/nested' should NOT have inherited I/CHANGE
+ $ret = file_ace_check($f2->smb_remotedir, $child_file_ace);
+ if ($ret == 0) {
+ say "unexpected ace";
+ return 1;
+ }
+
+ $f1->del_remote();
+ $f2->del_remote(1);
+
+ return 0;
+}
+
+# test smbcacls '--propagate-inheritance --delete' which attempts to delete the
+# ACL for the file and additionally use inheritance rules to propagate
+# appropriate changes to children
+#
+# This test adds an ACL with (CI)(NP)(CHANGE)
+# (NP) - no propagation should not propagate the changes any further containers
+#
+# before:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(I)(F), (CI)(NP)(CHANGE)
+# | +-file.1 (I)(F)
+# | +-nested/ (OI)(CI)(I)(F), (I)(CHANGE)
+# | +-file.2 (I)(F)
+#
+# after/expected:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(I)(F)
+# | +-file.1 (I)(F)
+# | +-nested/ (OI)(CI)(I)(F)
+# | +-file.2 (I)(F)
+
+sub test_simple_cinp_delete
+{
+ my @files;
+ my $dir_add_acl_str = "ACL:$USER:ALLOWED/CI|NP/CHANGE";
+ my $inherited_ace_str = "ACL:$USER:ALLOWED/I/CHANGE";
+ my $dir_ace;
+ my $child_file_ace;
+ my $child_dir_ace;
+ my $ret;
+ my $out;
+
+ my $f1 = File->new_local("$TMP/file-1");
+ $f1->put_remote("oi_dir/file-1");
+ my $f2 = File->new_local("$TMP/file-2");
+ $f2->put_remote("oi_dir/nested/file-2");
+
+ # set up ace(s) to match 'before'
+ ($out, $ret) = smb_cacls('--add', $dir_add_acl_str, $f1->smb_remotedir);
+ if ($ret != 0) {
+ return 1;
+ }
+ ($out, $ret) = smb_cacls('--add', $inherited_ace_str,
+ $f2->smb_remotedir);
+ if ($ret != 0) {
+ return 1;
+ }
+
+ ($out, $ret) = smb_cacls('--propagate-inheritance', '--delete',
+ $dir_add_acl_str, $f1->smb_remotedir);
+ if ($ret != 0) {
+ return 1;
+ }
+
+ # check top level container 'oi_dir' doesn't have CI|NP/READ
+ $dir_ace = ace_parse_str($dir_add_acl_str);
+ $ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+ if ($ret == 0) {
+ say "unexpected ace";
+ return 1;
+ }
+
+
+ $child_file_ace = ace_parse_str($inherited_ace_str);
+ # nested file 'oi_dir/file-1' should NOT have inherited I/CHANGE
+ $ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+ if ($ret == 0) {
+ say "unexpected ace";
+ return 1;
+ }
+
+ # nested dir 'oi_dir/nested' should NOT have I/CHANGE
+ $child_dir_ace = ace_parse_str($inherited_ace_str);
+ $ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+ if ($ret == 0) {
+ say "unexpected ace";
+ return 1;
+ }
+
+ $child_file_ace = ace_parse_str($inherited_ace_str);
+ # nested file 'oi_dir/nested/file-1' should NOT have inherited I/CHANGE
+ $ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+ if ($ret == 0) {
+ say "unexpected ace";
+ return 1;
+ }
+
+ $f1->del_remote();
+ $f2->del_remote(1);
+
+ return 0;
+}
+
+# test smbcacls '--propagate-inheritance --delete' which attempts to delete the
+# ACL for the file and additionally use inheritance rules to propagate
+# appropriate changes to children
+#
+# This test adds an ACL with (OI)(NP)(CHANGE)
+# (NP) - no propagation should not propagate the changes any further containers
+#
+# before:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(I)(F), (OI)(NP)(CHANGE)
+# | +-file.1 (I)(F), (I)(CHANGE)
+# | +-nested/ (OI)(CI)(I)(F)
+# | +-file.2 (I)(F)
+#
+# after/expected:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(I)(F)
+# | +-file.1 (I)(F)
+# | +-nested/ (OI)(CI)(I)(F)
+# | +-file.2 (I)(F)
+
+sub test_simple_oinp_delete
+{
+ my @files;
+ my $dir_add_acl_str = "ACL:$USER:ALLOWED/OI|NP/CHANGE";
+ my $inherited_ace_str = "ACL:$USER:ALLOWED/I/CHANGE";
+ my $dir_ace;
+ my $child_file_ace;
+ my $child_dir_ace;
+ my $ret;
+ my $out;
+
+ my $f1 = File->new_local("$TMP/file-1");
+ $f1->put_remote("oi_dir/file-1");
+ my $f2 = File->new_local("$TMP/file-2");
+ $f2->put_remote("oi_dir/nested/file-2");
+
+ # set up 'before' permissions
+ ($out, $ret) = smb_cacls('--add', $dir_add_acl_str, $f1->smb_remotedir);
+ if ($ret != 0) {
+ return 1;
+ }
+ ($out, $ret) = smb_cacls('--add', $inherited_ace_str,
+ $f1->smb_remotepath);
+ if ($ret != 0) {
+ return 1;
+ }
+
+ ($out, $ret) = smb_cacls('--propagate-inheritance', '--delete',
+ $dir_add_acl_str, $f1->smb_remotedir);
+ if ($ret != 0) {
+ return 1;
+ }
+
+ # check top level container 'oi_dir' does NOT have OI|NP/READ
+ $dir_ace = ace_parse_str($dir_add_acl_str);
+ $ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+ if ($ret == 0) {
+ say "unexpected ace";
+ return 1;
+ }
+
+
+ $child_file_ace = ace_parse_str($inherited_ace_str);
+ # nested file 'oi_dir/file-1' should NOT have inherited I/CHANGE
+ $ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+ if ($ret == 0) {
+ say "unexpected ace";
+ return 1;
+ }
+
+ $f1->del_remote();
+ $f2->del_remote(1);
+
+ return 0;
+}
+
+# test smbcacls '--propagate-inheritance --add' which attempts to add the ACL
+# for the file and additionally use inheritance rules to propagate appropriate
+# changes to children. In particular it tests that inheritance removed does
+# indeed prevent inheritance propagation
+#
+# This test adds an ACL with (CI)(OI)(CHANGE) at oi_dir
+#
+# Note: Inheritance has been removed ( and ace(s) copied ) at
+# tar_test_dir/oi_dir/nested
+#
+# before:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(I)(F)
+# | +-file.1 (I)(F)
+# | +-nested/ (OI)(CI)(F)
+# | +-file.2 (I)(F)
+#
+# after/expected:
+#
+# +-tar_test_dir/ (01)(CI)(I)(F)
+# +-oi_dir/ (OI)(CI)(I)(F), (CI)(OI)(CHANGE)
+# | +-file.1 (I)(F), (I)((CHANGE)
+# | +-nested/ (OI)(CI)(F)
+# | +-file.2 (I)(F)
+sub test_simple_cioi_inhibit
+{
+ my @files;
+ my $dir_add_acl_str = "ACL:$USER:ALLOWED/OI|CI/CHANGE";
+ my $file_inherited_ace_str = "ACL:$USER:ALLOWED/I/CHANGE";
+ my $dir_inherited_ace_str = "ACL:$USER:ALLOWED/OI|CI|I/CHANGE";
+ my $dir_ace;
+ my $child_file_ace;
+ my $child_dir_ace;
+ my $ret;
+ my $out;
+
+ my $f1 = File->new_local("$TMP/file-1");
+ $f1->put_remote("oi_dir/file-1");
+ my $f2 = File->new_local("$TMP/file-2");
+ $f2->put_remote("oi_dir/nested/file-2");
+
+ ($out, $ret) = smb_cacls('--inherit=copy', $f2->smb_remotedir);
+
+ # smb_cacls --inherit=remove
+ if ($ret != 0 ) {
+ say "failed to remove inheritance enabled";
+ return 1;
+ }
+
+ smb_cacls('--propagate-inheritance', '--add', $dir_add_acl_str,
+ $f1->smb_remotedir);
+
+ # check top level container 'oi_dir' has OI|CI/CHANGE
+ $dir_ace = ace_parse_str($dir_add_acl_str);
+ $ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+ if ($ret != 0) {
+ say "missing expected ace";
+ return 1;
+ }
+
+
+ $child_file_ace = ace_parse_str($file_inherited_ace_str);
+ # nested file 'oi_dir/file-1' should have inherited I/CHANGE
+ $ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+ if ($ret != 0) {
+ say "missing expected ace";
+ return 1;
+ }
+
+ # nested dir 'oi_dir/nested/' should NOT have OI|CI|I/CHANGE
+ $child_dir_ace = ace_parse_str($dir_inherited_ace_str);
+ $ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+ if ($ret == 0) {
+ say "unexpected ace";
+ return 1;
+ }
+
+ # nested file 'oi_dir/nested/file-2' should NOT have I/CHANGE
+ $child_dir_ace = ace_parse_str($dir_inherited_ace_str);
+ $ret = file_ace_check($f2->smb_remotepath, $child_dir_ace);
+ if ($ret == 0) {
+ say "unexpected ace";
+ return 1;
+ }
+ $f1->del_remote();
+ $f2->del_remote(1);
+
+ return 0;
+}
+
+#################################
+
+# IMPLEMENTATION
+
+=head2 Useful functions
+
+Here are a list of useful functions and helpers to define tests.
+
+=cut
+
+# list test number and description
+sub list_test {
+ my $i = 0;
+ for (@TESTS) {
+ my ($desc, $f, @args) = @$_;
+ printf "%2d.\t%s\n", $i++, $desc;
+ }
+}
+
+sub run_test {
+ if ($SUBUNIT) {
+ run_test_subunit(@_);
+ } else {
+ run_test_normal(@_);
+ }
+}
+
+sub run_test_normal {
+ for (@_) {
+ my ($desc, $f, @args) = @$_;
+ my $err;
+
+ reset_env();
+ say "TEST: $desc";
+ if ($VERBOSE) {
+ $err = $f->(@args);
+ } else {
+ # turn off STDOUT
+ open my $saveout, ">&STDOUT";
+ open STDOUT, '>', File::Spec->devnull();
+ $err = $f->(@args);
+ open STDOUT, ">&", $saveout;
+ }
+ print_res($err);
+ print "\n";
+ }
+ reset_env();
+}
+
+sub run_test_subunit {
+ for (@_) {
+ my ($desc, $f, @args) = @$_;
+ my $err;
+ my $str = '';
+
+ reset_env();
+ say "test: $desc";
+
+ # capture output in $buf
+ my $buf = '';
+ open my $handle, '>', \$buf;
+ select $handle;
+
+ # check for die() calls
+ eval {
+ $err = $f->(@args);
+ };
+ if ($@) {
+ $str = $@;
+ $err = 1;
+ }
+ close $handle;
+
+ # restore output
+ select STDOUT;
+
+ # result string is output + eventual exception message
+ $str = $buf.$str;
+
+ printf "%s: %s [\n%s]\n", ($err > 0 ? "failure" : "success"), $desc, $str;
+ }
+ reset_env();
+}
+
+sub parse_test_string {
+ my $s = shift;
+ my @tests = ();
+
+ if (!length($s)) {
+ return ();
+ }
+
+ for (split /,/, $s) {
+ if (/^\d+$/) {
+ if ($_ >= @TESTS) {
+ return ();
+ }
+ push @tests, $TESTS[$_];
+ }
+ elsif (/^(\d+)-(\d+)$/) {
+ my ($min, $max) = sort ($1, $2);
+ if ($max >= @TESTS) {
+ return ();
+ }
+
+ for ($min..$max) {
+ push @tests, $TESTS[$_];
+ }
+ }
+ else {
+ return ();
+ }
+ }
+
+ return @tests;
+}
+
+sub print_res {
+ my $err = shift;
+ if ($err) {
+ printf " RES: %s%d ERR%s\n", color('bold red'), $err, color 'reset';
+ } else {
+ printf " RES: %sOK%s\n", color('bold green'), color 'reset';
+ }
+}
+
+sub make_env {
+ my ($exts, $dirs) = @_;
+ my @all;
+ my $nb = 0;
+ for my $dir (@$dirs) {
+ for (@$exts) {
+ my $fn = $dir . "file$nb." . $_;
+ my $f = File->new_remote($fn, 'ABSPATH');
+ $f->delete_on_destruction(1);
+ push @all, $f;
+ $nb++;
+ }
+ }
+
+ @all;
+}
+
+=head3 C<combine ( \@set, $n )>
+
+=head3 C<combine ( ['a', 'b', 'c'], 2 )>
+
+Return a list of all possible I<n>-uplet (or combination of C<$n> element) of C<@set>.
+
+=cut
+sub combine {
+ my ($list, $n) = @_;
+ die "Insufficient list members" if $n > @$list;
+
+ return map [$_], @$list if $n <= 1;
+
+ my @comb;
+
+ for (my $i = 0; $i+$n <= @$list; $i++) {
+ my $val = $list->[$i];
+ my @rest = @$list[$i+1..$#$list];
+ push @comb, [$val, @$_] for combine(\@rest, $n-1);
+ }
+
+ return @comb;
+}
+
+
+=head3 C<reset_remote( )>
+
+Remove all files in the server C<$DIR> (not root)
+
+=cut
+sub reset_remote {
+ # remove_tree($LOCALPATH . '/'. $DIR);
+ # make_path($LOCALPATH . '/'. $DIR);
+ if ($CLEAN) {
+ remove_tree($LOCALPATH, {keep_root => 1});
+ }
+ make_path($LOCALPATH, {keep_root => 1});
+}
+
+=head3 C<reset_tmp( )>
+
+Remove all files in the temp directory C<$TMP>
+
+=cut
+sub reset_tmp {
+ remove_tree($TMP, { keep_root => 1 });
+}
+
+
+=head3 C<reset_env( )>
+
+Remove both temp and remote (C<$DIR>) files
+
+=cut
+sub reset_env {
+ reset_tmp();
+ reset_remote();
+}
+
+=head3 C<file_list ( @files )>
+
+Make a multiline string of all the files remote path, one path per line.
+
+C<@files> must be a list of C<File> instance.
+
+=cut
+sub file_list {
+ my @files = @_;
+ my $s = '';
+ for (@files) {
+ $s .= $_->remotepath."\n";
+ }
+ return $s;
+}
+
+# remove leading "./"
+sub remove_dot {
+ my $s = shift;
+ $s =~ s{^\./}{};
+ $s;
+}
+
+=head3 C<check_remote( $remotepath, \@files )>
+
+Check if C<$remotepath> has B<exactly> all the C<@files>.
+
+Print a summary on STDOUT.
+
+C<@files> must be a list of C<File> instance.
+
+=cut
+sub check_remote {
+ my ($subpath, $files) = @_;
+ my (%done, %expected);
+ my (@less, @more, @diff);
+
+ for (@$files) {
+ my $fn = remove_dot($_->remotepath);
+ $expected{$fn} = $_;
+ $done{$fn} = 0;
+ }
+
+ my %remote;
+ File::walk(sub { $remote{remove_dot($_->remotepath)} = $_ }, File::tree($subpath));
+
+ for my $rfile (sort keys %remote) {
+
+ # files that shouldn't be there
+ if (!exists $expected{$rfile}) {
+ say " + $rfile";
+ push @more, $rfile;
+ next;
+ }
+
+ # same file multiple times
+ if ($done{$rfile} > 0) {
+ $done{$rfile}++;
+ push @more, $rfile;
+ printf " +%3d %s\n", $done{$rfile}, $rfile;
+ next;
+ }
+
+ $done{$rfile}++;
+
+ # different file
+ my $rmd5 = $remote{$rfile}->md5;
+ if ($expected{$rfile}->md5 ne $rmd5) {
+ say " ! $rfile ($rmd5)";
+ push @diff, $rfile;
+ next;
+ }
+
+ say " $rfile";
+ }
+
+ # file that should have been in tar
+ @less = grep { $done{$_} == 0 } sort keys %done;
+ for (@less) {
+ say " - $_";
+ }
+
+ # summary
+ printf("\t%d files, +%d, -%d, !%d\n",
+ scalar keys %done,
+ scalar @more,
+ scalar @less,
+ scalar @diff);
+ return (@more + @less + @diff); # nb of errors
+}
+
+sub ace_dump
+{
+ my ($ace) = @_;
+ my $key;
+ my $value;
+
+ while (($key, $value) = each(%$ace)) {
+ print $key . "=" . $value . ", ";
+ }
+ print "\n";
+}
+
+sub ace_cmp
+{
+ my ($ace_left, $ace_right) = @_;
+ my $key;
+ my $value;
+ # need to copy hashes, otherwise entries are lost on failure!
+ my %ace1 = %$ace_left;
+ my %ace2 = %$ace_right;
+
+ while (($key, $value) = each(%ace1)) {
+ # ignore the users domain, only compare username
+ next if ($key eq "user_dom");
+
+ unless (defined $ace2{$key}) {
+ print "no entry for: $key\n";
+ return 1;
+ }
+ if ($value ne $ace2{$key}) {
+ print "mismatch: $key:$value != $key:$ace2{$key}\n";
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+=head3 C<ace_parse_str( $ace_str )>
+
+Parses the smbacls C<$ace_str> in the format of:
+ ACL:SAMBA-TEST\ddiss:ALLOWED/0x0/FULL
+The ACE is returned as hashref with the following members:
+->user_dom: Domain assigned to ACE user, if any
+->user: ACE user, without domain portion
+->type: allow or deny
+->inherit: inheritace flags
+->permissions: permissions flags
+
+=cut
+sub ace_parse_str
+{
+ my ($ace_str) = @_;
+ my %ace;
+
+ my @splat = split(":", $ace_str);
+
+ if ($splat[0] ne "ACL") {
+ say "invalid ACE string: $ace_str";
+ return undef;
+ }
+
+ #check for domain component in username
+ if (index($splat[1], "\\") >= 0) {
+ ($ace{user_dom}, $ace{user}) = split(/\\/, $splat[1]);
+ } elsif (index($splat[1], "/") >= 0) {
+ ($ace{user_dom}, $ace{user}) = split(m%/%, $splat[1]);
+ } else {
+ $ace{user} = $splat[1];
+ }
+
+ ($ace{type}, $ace{inherit}, $ace{permissions}) = split(/\//, $splat[2]);
+
+ return \%ace;
+}
+=head3 C<num_acls_for_file( $file )>
+
+count ACL that C<$file> has
+
+C<$file> must be a C<File> instance.
+
+=cut
+sub num_acls_for_file
+{
+ my ($file_remotepath) = @_;
+ my @aces;
+ my @out;
+ my ($output, $ret) = smb_cacls("--get", $file_remotepath);
+ @out = split("\n", $output);
+ @aces = grep(/^ACL/, @out);
+ return scalar @aces;
+}
+=head3 C<file_ace_check( $file, $ace )>
+
+Check that C<$file> has an ACE matching C<$ace>.
+
+C<$file> must be a C<File> instance.
+
+=cut
+sub file_ace_check
+{
+ my ($file_remotepath, $ace) = @_;
+ my $missing = 1;
+ my @aces;
+ my @out;
+ my ($output, $ret) = smb_cacls("--get", $file_remotepath);
+ @out = split("\n", $output);
+ @aces = grep(/^ACL/, @out);
+ # trim the domain portion of users
+ foreach (@aces) {
+ my $acl_ace = ace_parse_str($_);
+ return 1 unless defined $acl_ace;
+ # only compare aces for the same user and type
+ next unless (($acl_ace->{user} eq $ace->{user})
+ && ($acl_ace->{type} eq $ace->{type}));
+
+ say "found ACE for $acl_ace->{user}";
+ if (ace_cmp($acl_ace, $ace) != 0) {
+ print "differences between file ACE: ";
+ ace_dump($acl_ace);
+ print "and expected ACE: ";
+ ace_dump($ace);
+ }
+ else {
+ say "matched ACE for $acl_ace->{user}";
+ ace_dump($ace);
+ $missing = 0;
+ last;
+ }
+ }
+
+ return $missing;
+}
+
+=head3 C<smb_client ( @args )>
+
+Run smbclient with C<@args> passed as argument and return output.
+
+Each element of C<@args> becomes one escaped argument of smbclient.
+
+Host, share, user, password and the additionnal arguments provided on
+the command-line are already inserted.
+
+The output contains both the C<STDOUT> and C<STDERR>.
+
+Die if smbclient crashes or exits with an error code.
+
+=cut
+sub smb_client {
+ my (@args) = @_;
+
+ my $fullpath = "//$HOST/$SHARE";
+ my $cmd = sprintf("%s %s %s",
+ quotemeta($CLI_BIN),
+ quotemeta($fullpath),
+ join(' ', map {quotemeta} (@SMBARGS, @args)));
+
+ if ($DEBUG) {
+ my $tmp = $cmd;
+ $tmp =~ s{\\([./+-])}{$1}g;
+ say color('bold yellow'), $tmp, color('reset');
+ }
+
+ my $out = `$cmd 2>&1`;
+ my $err = $?;
+ my $errstr = '';
+ # handle abnormal exit
+ if ($err == -1) {
+ $errstr = "failed to execute $cmd: $!\n";
+ }
+ elsif ($err & 127) {
+ $errstr = sprintf "child died with signal %d (%s)\n", ($err & 127), $cmd;
+ }
+ elsif ($err >> 8) {
+ $errstr = sprintf "child exited with value %d (%s)\n", ($err >> 8), $cmd;
+ }
+
+ if ($DEBUG) {
+ say $out;
+ }
+
+ if ($err) {
+ die "ERROR: $errstr";
+ }
+ return $out;
+}
+
+=head3 C<smb_cacls ( @args )>
+
+Run smbcacls with C<@args> passed as argument and return output.
+
+Each element of C<@args> becomes one escaped argument of smbcacls.
+
+Host, share, user, password and the additionnal arguments provided on
+the command-line are already inserted.
+
+The output contains both the C<STDOUT> and C<STDERR>.
+
+Die if smbcacls crashes or exits with an error code.
+
+=cut
+sub smb_cacls
+{
+ my (@args) = @_;
+
+ # last arg is the filename, share path must be immediately prior
+ my $file = pop(@args);
+ my $sharepath = "//$HOST/$SHARE";
+ my $cmd = sprintf("%s %s %s %s",
+ quotemeta($CACLS_BIN),
+ join(' ', map {quotemeta} (@SMBARGS, @args)),
+ quotemeta($sharepath),
+ quotemeta($file));
+
+ if ($DEBUG) {
+ my $tmp = $cmd;
+ $tmp =~ s{\\([./+-])}{$1}g;
+ say color('bold yellow'), $tmp, color('reset');
+ }
+
+ my $out = `$cmd 2>&1`;
+ my $err = $?;
+ my $errstr = '';
+ my $ret = 0;
+ # handle abnormal exit
+ if ($err == -1) {
+ $errstr = "failed to execute $cmd: $!\n";
+ } elsif ($err & 127) {
+ $errstr = sprintf "child died with signal %d (%s)\n", ($err & 127), $cmd;
+ } elsif ($err >> 8) {
+ $errstr = sprintf "child exited with value %d (%s)\n", ($err >> 8), $cmd;
+ $ret = ($err >> 8);
+ }
+
+ if ($DEBUG) {
+ say $out;
+ }
+
+ if ($err) {
+ # die for abnormal error (cmd didn't run or some signal caught)
+ die "ERROR: $errstr" if $ret == 0;
+ }
+ return ($out, $ret);
+}
+
+=head3 C<random( $min, $max )>
+
+Return integer in C<[ $min ; $max ]>
+
+=cut
+sub random {
+ my ($min, $max) = @_;
+ ($min, $max) = ($max, $min) if ($min > $max);
+ $min + int(rand($max - $min));
+}
+
+#################################
+
+package File;
+
+=head2 The File module
+
+All the test should use the C<File> class. It has nice functions and
+methods to deal with paths, to create random files, to list the
+content of the server, to change attributes, etc.
+
+There are 2 kinds of C<File>: remote and local.
+
+=over
+
+=item * Remote files are accessible on the server.
+
+=item * Local files are not.
+
+=back
+
+Thus, some methods only works on remote files. If they do not make
+sense for local ones, they always return undef.
+
+=cut
+use File::Basename;
+use File::Path qw/make_path remove_tree/;
+use Digest::MD5 qw/md5_hex/;
+use Scalar::Util 'blessed';
+
+=head3 Constructors
+
+=head4 C<< File->new_remote($path [, $abs, $size]) >>
+
+Creates a file accessible on the server at C<$DIR/$path> ie. not at the
+root of the share and write C<$size> random bytes.
+
+If no size is provided, a random size is chosen.
+
+If you want to remove the automatic prefix C<$DIR>, set C<$abs> to 1.
+
+The file is created without any DOS attributes.
+
+If C<$path> contains non-existent directories, they are automatically
+created.
+
+=cut
+sub new_remote {
+ my ($class, $path, $abs, $size) = @_;
+ my ($file, $dir) = fileparse($path);
+
+ $dir = '' if $dir eq './';
+ my $loc;
+
+ if ($abs) {
+ $loc = cleanpath($main::LOCALPATH.'/'.$dir);
+ } else {
+ $dir = cleanpath($main::DIR.'/'.$dir);
+ $loc = cleanpath($main::LOCALPATH.'/'.$dir);
+ }
+
+ make_path($loc);
+
+ my $self = bless {
+ 'attr' => {qw/r 0 s 0 h 0 a 0 d 0 n 0/},
+ 'dir' => $dir,
+ 'name' => $file,
+ 'md5' => create_file($loc.'/'.$file, $size),
+ 'remote' => 1,
+ }, $class;
+
+ $self->set_attr();
+
+ $self;
+}
+
+=head4 C<< File->new_local($abs_path [, $data]) >>
+
+Creates a file at C<$abs_path> with $data in it on the system.
+If $data is not provided, fill it with random bytes.
+
+=cut
+sub new_local {
+ my ($class, $path, $data) = @_;
+ my ($file, $dir) = fileparse($path);
+
+ make_path($dir);
+
+ my $md5;
+
+ if (defined $data) {
+ open my $f, '>', $path or die "can't write in $path: $!";
+ print $f $data;
+ close $f;
+ $md5 = md5_hex($data);
+ } else {
+ $md5 = create_file($path);
+ }
+
+ my $self = {
+ 'attr' => {qw/r 0 s 0 h 0 a 0 d 0 n 0/},
+ 'dir' => $dir,
+ 'name' => $file,
+ 'md5' => $md5,
+ 'remote' => 0,
+ };
+
+ bless $self, $class;
+}
+
+=head3 Methods
+
+=head4 C<< $f->localpath >>
+
+Return path on the system eg. F</opt/samba/share/test_tar_mode/file>
+
+=cut
+sub localpath {
+ my $s = shift;
+ if ($s->{remote}) {
+ return cleanpath($main::LOCALPATH.'/'.$s->remotepath);
+ }
+ else {
+ return cleanpath($s->{dir}.'/'.$s->{name});
+ }
+}
+
+=head4 C<< $f->remotepath >>
+
+Return path on the server share.
+
+Return C<undef> if the file is local.
+
+=cut
+sub remotepath {
+ my ($s) = @_;
+ return undef if !$s->{remote};
+ my $r = $s->{dir}.'/'.$s->{name};
+ $r =~ s{^/}{};
+ return cleanpath($r);
+}
+
+=head4 C<< $f->smb_remotepath >>
+
+Return SMB path on the server share.
+
+Return C<undef> if the file is local.
+
+=cut
+sub smb_remotepath {
+ my ($s) = @_;
+ return undef if !$s->{remote};
+ my $r = $s->remotepath;
+ $r =~ s{/}{\\}g;
+ return $r;
+}
+
+=head4 C<< $f->remotedir >>
+
+Return the directory path of the file on the server.
+
+Like C<< $f->remotepath >> but without the final file name.
+
+=cut
+sub remotedir {
+ my $s = shift;
+ return undef if !$s->{remote};
+ cleanpath($s->{dir});
+}
+
+=head4 C<< $f->smb_remotepath >>
+
+Return SMB directory on the server share.
+
+Return C<undef> if the file is local.
+
+=cut
+sub smb_remotedir {
+ my ($s) = @_;
+ return undef if !$s->{remote};
+ my $r = $s->remotedir;
+ $r =~ s{/}{\\}g;
+ return $r;
+}
+
+=head4 C<< $f->tarpath >>
+
+Return path as it would appear in a tar archive.
+
+Like C<< $f->remotepath >> but prefixed with F<./>
+
+=cut
+sub tarpath {
+ my $s = shift;
+ return undef if !$s->{remote};
+ my $r = $s->remotepath;
+ $r =~ s{^\./+}{};
+ return $r;
+}
+
+=head4 C<< $f->delete_on_destruction( 0 ) >>
+
+=head4 C<< $f->delete_on_destruction( 1 ) >>
+
+By default, a C<File> is not deleted on the filesystem when it is not
+referenced anymore in Perl memory.
+
+When set to 1, the destructor unlink the file if it is not already removed.
+If the C<File> created directories when constructed, it does not remove them.
+
+=cut
+sub delete_on_destruction {
+ my ($s, $delete) = @_;
+ $s->{delete_on_destruction} = $delete;
+}
+
+=head4 C<< $f->set_attr( ) >>
+
+=head4 C<< $f->set_attr( 'a' ) >>
+
+=head4 C<< $f->set_attr( 'a', 'r', 's', 'h' ) >>
+
+Remove all DOS attributes and only set the one provided.
+
+=cut
+sub set_attr {
+ my ($s, @flags) = @_;
+ return undef if !$s->{remote};
+
+ $s->{attr} = {qw/r 0 s 0 h 0 a 0 d 0 n 0/};
+
+ for (@flags) {
+ $s->{attr}{lc($_)} = 1;
+ }
+
+ my $file = $s->{name};
+ my @args;
+ if ($s->remotedir) {
+ push @args, '-D', $s->remotedir;
+ }
+ main::smb_client(@args, '-c', qq{setmode "$file" -rsha});
+ if (@flags && $flags[0] !~ /n/i) {
+ main::smb_client(@args, '-c', qq{setmode "$file" +}.join('', @flags));
+ }
+}
+
+=head4 C<< $f->attr_any( 'a' ) >>
+
+=head4 C<< $f->attr_any( 'a', 's', ... ) >>
+
+Return 1 if the file has any of the DOS attributes provided.
+
+=cut
+sub attr_any {
+ my ($s, @flags) = @_;
+ for (@flags) {
+ return 1 if $s->{attr}{$_};
+ }
+ 0;
+}
+
+
+=head4 C<< $f->attr( 'a' ) >>
+
+=head4 C<< $f->attr( 'a', 's', ... ) >>
+
+Return 1 if the file has all the DOS attributes provided.
+
+=cut
+sub attr {
+ my ($s, @flags) = @_;
+ for (@flags) {
+ return 0 if !$s->{attr}{$_};
+ }
+ 1;
+}
+
+=head4 C<< $f->attr_str >>
+
+Return DOS attributes as a compact string.
+
+ Read-only, hiden, system, archive => "rhsa"
+
+=cut
+sub attr_str {
+ my $s = shift;
+ return undef if !$s->{remote};
+ join('', map {$_ if $s->{attr}{$_}} qw/r h s a d n/);
+}
+
+=head4 C<< $f->set_time($t) >>
+
+Set modification and access time of the file to C<$t>.
+
+C<$t> must be in Epoch time (number of seconds since 1970/1/1).
+
+=cut
+sub set_time {
+ my ($s, $t) = @_;
+ utime $t, $t, $s->localpath;
+}
+
+=head4 C<< $f->md5 >>
+
+Return md5 sum of the file.
+
+The result is cached.
+
+=cut
+sub md5 {
+ my $s = shift;
+
+ if (!$s->{md5}) {
+ open my $h, '<', $s->localpath() or die "can't read ".$s->localpath.": $!";
+ binmode $h;
+ $s->{md5} = Digest::MD5->new->addfile($h)->hexdigest;
+ close $h;
+ }
+
+ return $s->{md5};
+}
+
+=head4 C<< $f->put_remote($path, [$abs]) >>
+
+Transfers an existing local file to the server at C<$DIR/$path>.
+The local file is removed on success.
+
+=cut
+sub put_remote
+{
+ my ($s, $path, $abs) = @_;
+ my ($file, $dir) = fileparse($path);
+ my $remote_path = "";
+
+ return undef if $s->{remote};
+
+ $dir = '' if $dir eq './';
+ my $loc;
+
+ if ($abs) {
+ $loc = cleanpath($main::LOCALPATH.'/'.$dir);
+ } else {
+ $dir = cleanpath($main::DIR.'/'.$dir);
+ $loc = cleanpath($main::LOCALPATH.'/'.$dir);
+ }
+
+ if ($dir) {
+ foreach (split("\/", $dir)) {
+ $remote_path .= $_ . "\\";
+ main::smb_client('-c', qq{mkdir "$remote_path"});
+ }
+ }
+
+ $remote_path .= $file;
+ my $local_path = $s->localpath();
+ main::smb_client('-c', qq{put "$local_path" "$remote_path"});
+
+ $s->{dir} = $dir;
+ $s->{name} = $file;
+ $s->{remote} = 1;
+
+ return 0;
+}
+
+=head4 C<< $f->del_remote() >>
+
+Delete remote file.
+
+=cut
+sub del_remote
+{
+ my ($s, $rm_parent_dirs) = @_;
+
+ return 1 unless $s->{remote};
+
+ my $remote_path = $s->smb_remotepath;
+
+ main::smb_client('-c', qq{rm "$remote_path"});
+
+ unless ($rm_parent_dirs) {
+ return 0;
+ }
+
+ # attempt to remove all parent dirs
+ my @cmps = split(/\\/, $s->smb_remotedir);
+ while (scalar(@cmps) > 0) {
+ my $path = join("\\", @cmps);
+ main::smb_client('-c', qq{rmdir "$path"});
+ pop(@cmps);
+ }
+
+ return 0;
+}
+
+sub DESTROY {
+ my $s = shift;
+ if ($s->{delete_on_destruction} && -f $s->localpath) {
+ if ($main::DEBUG) {
+ say "DESTROY ".$s->localpath;
+ }
+ unlink $s->localpath;
+ }
+}
+
+=head3 Functions
+
+=head4 C<< File::walk( \&function, @files) >>
+
+=head4 C<< File::walk( sub { ... }, @files) >>
+
+Iterate on file hierachy in C<@files> and return accumulated results.
+
+Use C<$_> in the sub to access the current C<File>.
+
+The C<@files> must come from a call to the C<File::tree> function.
+
+=cut
+sub walk {
+ my $fun = \&{shift @_};
+
+ my @res;
+
+ for (@_) {
+ if ($_->{attr}{d}) {
+ push @res, walk($fun, @{$_->{content}});
+ } else {
+ push @res, $fun->($_);
+ }
+ }
+
+ return @res;
+}
+
+=head4 C<< File::list( $remotepath ) >>
+
+Return list of file (C<File> instance) in C<$remotepath>.
+
+C<$remotepath> must be a directory.
+
+=cut
+sub list {
+ my ($path) = @_;
+ $path ||= '/';
+ my @files;
+ my $out = main::smb_client('-D', $path, '-c', 'ls');
+ $path =~ s{^/}{};
+
+ for (split /\n/, $out) {
+ next if !/^ (.+?)\s+([AHSRDN]*)\s+(\d+)\s+(.+)/o;
+ my ($fn, $attr, $size, $date) = ($1, $2, $3, $4);
+ next if $fn =~ /^\.{1,2}$/;
+
+ push @files, bless {
+ 'remote' => 1,
+ 'dir' => $path,
+ 'name' => $fn,
+ 'size' => int($size),
+ 'date' => $date,
+ 'attr' => {
+ # list context returns something different than the
+ # boolean matching result => force scalar context
+ 'a' => scalar ($attr =~ /A/),
+ 'h' => scalar ($attr =~ /H/),
+ 's' => scalar ($attr =~ /S/),
+ 'r' => scalar ($attr =~ /R/),
+ 'd' => scalar ($attr =~ /D/),
+ 'n' => scalar ($attr =~ /N/),
+ },
+ }, 'File';
+ }
+ return @files;
+}
+
+=head4 C<< File::tree( $remotepath ) >>
+
+Return recursive list of file in C<$remotepath>.
+
+C<$remotepath> must be a directory.
+
+Use C<File::walk()> to iterate over all the files.
+
+=cut
+sub tree {
+ my ($d) = @_;
+ my @files;
+
+ if (!defined $d) {
+ @files = list();
+ } elsif (blessed $d) {
+ @files = list($d->remotepath);
+ } else {
+ @files = list($d);
+ }
+
+ for my $f (@files) {
+ if ($f->{attr}{d}) {
+ $f->{content} = [tree($f)];
+ }
+ }
+
+ return @files;
+}
+
+# remove trailing or duplicated slash
+sub cleanpath {
+ my $p = shift;
+ $p =~ s{/+}{/}g;
+ $p =~ s{/$}{};
+ $p;
+}
+
+# create random file at path local path $fn
+sub create_file {
+ my ($fn, $size) = @_;
+ my $buf = '';
+ unlink $fn if -e $fn;
+ $size ||= main::random(512, 1024);
+ $size = int($size);
+ my $md5;
+
+ # try /dev/urandom, faster
+ if (-e '/dev/urandom') {
+ my $cmd = sprintf('head -c %d /dev/urandom | tee %s | md5sum',
+ $size, quotemeta($fn));
+ $md5 = (split / /, `$cmd`)[0];
+ } else {
+ open my $out, '>', $fn or die "can't open $fn: $!\n";
+ binmode $out;
+ for (1..$size) {
+ $buf .= pack('C', main::random(0, 256));
+ }
+ print $out $buf;
+ close $out;
+ $md5 = md5_hex($buf);
+ }
+ return $md5;
+}
+
+
+=head3 Examples
+
+ # create remote file in $DIR/foo/bar
+ my $f = File->new_remote("foo/bar/myfile");
+ say $f->localpath; # /opt/share/$DIR/foo/bar/myfile
+ say $f->remotepath; # $DIR/foo/bar/myfile
+ say $f->remotedir; # $DIR/foo/bar
+
+
+ # same but in root dir
+ my $f = File->new_remote("myfile", 1);
+ say $f->localpath; # /opt/share/myfile
+ say $f->remotepath; # myfile
+ say $f->remotedir; #
+
+
+ # create local random temp file in $TMP
+ my $f = File->new_local("$TMP/temp");
+ say $f->remotepath; # undef because it's not on the server
+
+
+ # same but file contains "hello"
+ my $f = File->new_local("$TMP/temp", "hello");
+
+
+ # list of files in $DIR (1 level)
+ for (File::list($DIR)) {
+ say $_->remotepath;
+ }
+
+
+ # list of all files in dir and subdir of $DIR
+ File::walk(sub { say $_->remotepath }, File::tree($DIR));
+
+=cut
+
+1;
diff --git a/source3/selftest/tests.py b/source3/selftest/tests.py
index 3a6186c..c6ba2fe 100755
--- a/source3/selftest/tests.py
+++ b/source3/selftest/tests.py
@@ -242,6 +242,28 @@ for env in ["fileserver"]:
'-d', '$PREFIX', '-b', smbclient3,
'--subunit', '--', configuration])
+ # Test smbcacls
+ smbcacls_test_args = [os.path.join(samba3srcdir, "script/tests/test_smbcacls.pl"),
+ '-n', '$SERVER', '-s', 'tmp', '-u', '$USERNAME',
+ '-p', '$PASSWORD', '-l', '$LOCAL_PATH', '-d', '$PREFIX',
+ '--cli_bin=' + smbclient3, '-cacls_bin=' + smbcacls,
+ '--subunit', '--clean']
+ smbcacls_subtests = ["file_set", "file_mod", "file_del", "file_add",
+ "oi_add", "oi_del", "oi_mod",
+ "ci_add", "ci_del", "ci_mod",
+ "coi_add", "coi_del", "coi_mod",
+ "inherit_set", "coi_set", "ci_set",
+ "coinp_add", "oinp_add", "cinp_add",
+ "coinp_delete", "oinp_delete", "cinp_delete",
+ "coi_inhibit"]
+
+ i = 0
+ for cacls_subtest in smbcacls_subtests:
+ plantestsuite("samba3.blackbox.smbcacls." + cacls_subtest + " (%s)"
+ % env, env, smbcacls_test_args
+ + ['--test', str(i), '--', configuration])
+ i += 1
+
#TODO encrypted against member, with member creds, and with DC creds
plantestsuite("samba3.blackbox.net.misc", "nt4_dc:local",
[os.path.join(samba3srcdir, "script/tests/test_net_misc.sh"),
--
2.10.2
From 64874618d86a1eb38f14743ce7f8c2e1252f463a Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Thu, 14 Nov 2013 17:45:07 +0000
Subject: [PATCH 2/4] add new '--propagate-inheritance' option for smbcacls
smbcacls now can take a '--propagate-inheritance' flag to indicate that the
add, delete, modify and set operations now support automatic propagation of
inheritable ACE(s)
Signed-off-by: Noel Power <noel.power at suse.com>
---
source3/utils/smbcacls.c | 706 +++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 689 insertions(+), 17 deletions(-)
diff --git a/source3/utils/smbcacls.c b/source3/utils/smbcacls.c
index 255ff97..0a687c8 100644
--- a/source3/utils/smbcacls.c
+++ b/source3/utils/smbcacls.c
@@ -6,6 +6,7 @@
Copyright (C) Tim Potter 2000
Copyright (C) Jeremy Allison 2000
Copyright (C) Jelmer Vernooij 2003
+ Copyright (C) Noel Power <noel.power at suse.com> 2013
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -33,10 +34,13 @@
#include "../librpc/gen_ndr/ndr_lsa_c.h"
#include "util_sd.h"
+static char DIRSEP_CHAR = '\\';
+
static int test_args;
#define CREATE_ACCESS_READ READ_CONTROL_ACCESS
+static int inheritance = 0;
static int sddl;
static int query_sec_info = -1;
static int set_sec_info = -1;
@@ -47,6 +51,18 @@ enum acl_mode {SMB_ACL_SET, SMB_ACL_DELETE, SMB_ACL_MODIFY, SMB_ACL_ADD };
enum chown_mode {REQUEST_NONE, REQUEST_CHOWN, REQUEST_CHGRP, REQUEST_INHERIT};
enum exit_values {EXIT_OK, EXIT_FAILED, EXIT_PARSE_ERROR};
+struct cacl_callback_state {
+ struct user_auth_info *auth_info;
+ struct cli_state *cli;
+ struct security_descriptor *aclsd;
+ struct security_acl *acl_to_add;
+ enum acl_mode mode;
+ char *the_acl;
+ bool acl_no_propagate;
+ bool inherit_enabled;
+ bool numeric;
+};
+
static NTSTATUS cli_lsa_lookup_domain_sid(struct cli_state *cli,
struct dom_sid *sid)
{
@@ -123,12 +139,14 @@ static struct dom_sid *get_domain_sid(struct cli_state *cli)
}
/* add an ACE to a list of ACEs in a struct security_acl */
-static bool add_ace(struct security_acl **the_acl, struct security_ace *ace)
+static bool add_ace_with_ctx(TALLOC_CTX *ctx, struct security_acl **the_acl,
+ struct security_ace *ace)
+
{
struct security_acl *new_ace;
struct security_ace *aces;
if (! *the_acl) {
- return (((*the_acl) = make_sec_acl(talloc_tos(), 3, 1, ace))
+ return (((*the_acl) = make_sec_acl(ctx, 3, 1, ace))
!= NULL);
}
@@ -138,12 +156,18 @@ static bool add_ace(struct security_acl **the_acl, struct security_ace *ace)
memcpy(aces, (*the_acl)->aces, (*the_acl)->num_aces * sizeof(struct
security_ace));
memcpy(aces+(*the_acl)->num_aces, ace, sizeof(struct security_ace));
- new_ace = make_sec_acl(talloc_tos(),(*the_acl)->revision,1+(*the_acl)->num_aces, aces);
+ new_ace = make_sec_acl(ctx, (*the_acl)->revision,
+ 1+(*the_acl)->num_aces, aces);
SAFE_FREE(aces);
(*the_acl) = new_ace;
return True;
}
+static bool add_ace(struct security_acl **the_acl, struct security_ace *ace)
+{
+ return add_ace_with_ctx(talloc_tos(), the_acl, ace);
+}
+
/* parse a ascii version of a security descriptor */
static struct security_descriptor *sec_desc_parse(TALLOC_CTX *ctx, struct cli_state *cli, char *str)
{
@@ -250,7 +274,9 @@ static uint16_t get_fileinfo(struct cli_state *cli, const char *filename)
/*****************************************************
get sec desc for filename
*******************************************************/
-static struct security_descriptor *get_secdesc(struct cli_state *cli, const char *filename)
+static struct security_descriptor *get_secdesc_with_ctx(TALLOC_CTX *ctx,
+ struct cli_state *cli,
+ const char *filename)
{
uint16_t fnum = (uint16_t)-1;
struct security_descriptor *sd;
@@ -284,7 +310,7 @@ static struct security_descriptor *get_secdesc(struct cli_state *cli, const char
}
status = cli_query_security_descriptor(cli, fnum, sec_info,
- talloc_tos(), &sd);
+ ctx, &sd);
cli_close(cli, fnum);
@@ -296,6 +322,11 @@ static struct security_descriptor *get_secdesc(struct cli_state *cli, const char
return sd;
}
+static struct security_descriptor *get_secdesc(struct cli_state *cli,
+ const char *filename)
+{
+ return get_secdesc_with_ctx(talloc_tos(), cli, filename);
+}
/*****************************************************
set sec desc for filename
*******************************************************/
@@ -485,23 +516,18 @@ static void sort_acl(struct security_acl *the_acl)
}
/*****************************************************
-set the ACLs on a file given an ascii description
+set the ACLs on a file given a security descriptor
*******************************************************/
-static int cacl_set(struct cli_state *cli, const char *filename,
- char *the_acl, enum acl_mode mode, bool numeric)
+static int cacl_set_from_sd(struct cli_state *cli, const char *filename,
+ struct security_descriptor *sd, enum acl_mode mode,
+ bool numeric)
{
- struct security_descriptor *sd, *old;
+ struct security_descriptor *old;
uint32_t i, j;
size_t sd_size;
int result = EXIT_OK;
- if (sddl) {
- sd = sddl_decode(talloc_tos(), the_acl, get_domain_sid(cli));
- } else {
- sd = sec_desc_parse(talloc_tos(), cli, the_acl);
- }
-
if (!sd) return EXIT_PARSE_ERROR;
if (test_args) return EXIT_OK;
@@ -773,6 +799,633 @@ static struct cli_state *connect_one(const struct user_auth_info *auth_info,
return c;
}
+/*
+ * Process mntpoint and ensure resulting combination of mntpoint, mask & fname
+ * is terminated with wildcard
+ */
+static char *build_dirname(TALLOC_CTX *ctx, const char *mntpoint,
+ const char *mask, char *dir, char *fname)
+{
+ char *mask2 = NULL;
+ char *p = NULL;
+
+ mask2 = talloc_asprintf(ctx,
+ "%s%s",
+ mntpoint,
+ mask);
+ if (!mask2) {
+ return NULL;
+ }
+ p = strrchr_m(mask2, DIRSEP_CHAR);
+ if (p) {
+ p[1] = 0;
+ } else {
+ mask2[0] = '\0';
+ }
+ mask2 = talloc_asprintf_append(mask2,
+ "%s\\*",
+ fname);
+ return mask2;
+}
+
+/*
+ * Returns the a copy of the ACL flags in ace modified according
+ * to some inheritance rules.
+ * a) SEC_ACE_FLAG_INHERITED_ACE is propagated to children
+ * b) SEC_ACE_FLAG_INHERIT_ONLY is set on container children for OI (only)
+ * c) SEC_ACE_FLAG_OBJECT_INHERIT & SEC_ACE_FLAG_CONTAINER_INHERIT are
+ * stripped from flags to be propagated to non-container children
+ * d) SEC_ACE_FLAG_OBJECT_INHERIT & SEC_ACE_FLAG_CONTAINER_INHERIT are
+ * stripped from flags to be propagated if the NP flag
+ * SEC_ACE_FLAG_NO_PROPAGATE_INHERIT is present
+ */
+
+static uint8_t get_flags_to_propagate(bool is_container,
+ struct security_ace *ace)
+{
+ uint8_t newflags = ace->flags;
+ /* OBJECT inheritance */
+ bool acl_objinherit = (ace->flags &
+ SEC_ACE_FLAG_OBJECT_INHERIT) == SEC_ACE_FLAG_OBJECT_INHERIT;
+ /* CONTAINER inheritance */
+ bool acl_cntrinherit = (ace->flags &
+ SEC_ACE_FLAG_CONTAINER_INHERIT) ==
+ SEC_ACE_FLAG_CONTAINER_INHERIT;
+ /* PROHIBIT inheritance */
+ bool prohibit_inheritance = ((ace->flags &
+ SEC_ACE_FLAG_NO_PROPAGATE_INHERIT) ==
+ SEC_ACE_FLAG_NO_PROPAGATE_INHERIT);
+
+ /* all children need to have the SEC_ACE_FLAG_INHERITED_ACE set */
+ if (acl_cntrinherit || acl_objinherit) {
+ newflags |= SEC_ACE_FLAG_INHERITED_ACE;
+ /*
+ * object inherit ( alone ) on a container needs
+ * SEC_ACE_FLAG_INHERIT_ONLY
+ */
+ if (is_container && acl_objinherit &&
+ !acl_cntrinherit) {
+ newflags |= SEC_ACE_FLAG_INHERIT_ONLY;
+ }
+ if (!is_container || prohibit_inheritance) {
+ /*
+ * don't apply object/container inheritance flags to
+ * non dirs or if instructed not to propagate
+ */
+ newflags &= ~(SEC_ACE_FLAG_OBJECT_INHERIT
+ | SEC_ACE_FLAG_CONTAINER_INHERIT
+ | SEC_ACE_FLAG_INHERIT_ONLY);
+ /*
+ * don't propagate INHERIT_ONLY (from parent) either */
+ if (!is_container) {
+ newflags &= ~SEC_ACE_FLAG_INHERIT_ONLY;
+ }
+ if (prohibit_inheritance) {
+ newflags &= ~SEC_ACE_FLAG_NO_PROPAGATE_INHERIT;
+ }
+ }
+ } else {
+ newflags &= ~SEC_ACE_FLAG_INHERITED_ACE;
+ }
+ return newflags;
+}
+
+/*
+ * This function builds a new acl for 'caclfile', first it removes any
+ * existing inheritable ace(s) from the current acl of caclfile, secondly it
+ * applies any inheritable acls of the parent of caclfile ( inheritable acls of
+ * caclfile's parent are passed via acl_to_add member of cbstate )
+ *
+ */
+static NTSTATUS propagate_inherited_aces(char *caclfile,
+ struct cacl_callback_state *cbstate)
+{
+ TALLOC_CTX *aclctx;
+ NTSTATUS status = NT_STATUS_OK;
+ int result;
+ int fileattr = 0;
+ struct security_descriptor *old;
+ bool is_container = false;
+ struct security_acl *acl_to_add = cbstate->acl_to_add;
+ struct security_acl *explicit_aces = NULL;
+ uint32_t i, j;
+
+ aclctx = talloc_new(NULL);
+ if (!aclctx) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ old = get_secdesc_with_ctx(aclctx, cbstate->cli, caclfile);
+
+ if (!old) {
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto out;
+ }
+
+ /* inhibit propagation? */
+ if (old->type & SEC_DESC_DACL_PROTECTED) {
+ status = NT_STATUS_OK;
+ goto out;
+ }
+
+ fileattr = get_fileinfo(cbstate->cli, caclfile);
+ is_container = (fileattr & FILE_ATTRIBUTE_DIRECTORY);
+
+ /* find explict ace(s) */
+ for (j = 0; old->dacl && j < old->dacl->num_aces; j++) {
+
+ if (!(old->dacl->aces[j].flags & SEC_ACE_FLAG_INHERITED_ACE)) {
+ if (!add_ace_with_ctx(aclctx, &explicit_aces,
+ &old->dacl->aces[j])) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+ }
+
+ /* remove all old ace(s) from security descriptor */
+ old->dacl->num_aces = 0;
+
+ /* add the explict aces to security descriptor */
+ for (i = 0;explicit_aces && i < explicit_aces->num_aces; i++) {
+ struct security_ace ace = explicit_aces->aces[i];
+ if (!add_ace_with_ctx(aclctx, &old->dacl, &ace)) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+
+ /* propagate any inheritable ace(s) to be added */
+ for (i = 0; acl_to_add && i < acl_to_add->num_aces; i++) {
+ struct security_ace ace = acl_to_add->aces[i];
+ bool is_objectinherit = (ace.flags &
+ SEC_ACE_FLAG_OBJECT_INHERIT) ==
+ SEC_ACE_FLAG_OBJECT_INHERIT;
+ bool is_inherited = false;
+ /* don't propagate flags to a file unless OI */
+ if (!is_objectinherit && !is_container) {
+ continue;
+ }
+ /*
+ * adjust flags according to inheritance
+ * rules
+ */
+ ace.flags = get_flags_to_propagate(is_container, &ace);
+ is_inherited = (ace.flags &
+ SEC_ACE_FLAG_INHERITED_ACE) ==
+ SEC_ACE_FLAG_INHERITED_ACE;
+ /* don't propagate non inherited flags */
+ if (!is_inherited) {
+ continue;
+ }
+ if (!add_ace_with_ctx(aclctx, &old->dacl, &ace)) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+
+ result = cacl_set_from_sd(cbstate->cli, caclfile,
+ old,
+ SMB_ACL_SET, cbstate->numeric);
+ if (result != EXIT_OK) {
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto out;
+ }
+
+out:
+ TALLOC_FREE(aclctx);
+ return status;
+}
+
+/*
+ * Returns true if 'ace' contains SEC_ACE_FLAG_OBJECT_INHERIT or
+ * SEC_ACE_FLAG_CONTAINER_INHERIT, if inherited_only is true then
+ * additionally SEC_ACE_FLAG_INHERITED_ACE must be set
+ */
+static bool is_inheritable_ace(struct security_ace *ace, bool inherited_only)
+{
+ uint8_t flags = ace->flags;
+
+ flags &= (SEC_ACE_FLAG_OBJECT_INHERIT
+ | SEC_ACE_FLAG_CONTAINER_INHERIT
+ | SEC_ACE_FLAG_INHERITED_ACE);
+ if (inherited_only) {
+ /* only consider (OI) and/or (CI) flags WITH (I) */
+ return (flags & SEC_ACE_FLAG_INHERITED_ACE);
+ }
+ return (flags > 0);
+}
+
+/* This method does some basic sanity checking with respect to automatic
+ * inheritance. e.g. it checks if it is possible to do a set, it detects illegal
+ * attempts to set inherited permissions directly. Additionally this method
+ * does some basic initialisation for instance it parses the ACL passed on the
+ * command line.
+ */
+static NTSTATUS prepare_inheritance_propagation(TALLOC_CTX *ctx, char *filename,
+ struct cacl_callback_state *cbstate)
+{
+ NTSTATUS result = NT_STATUS_OK;
+ char *the_acl = cbstate->the_acl;
+ struct cli_state *cli = cbstate->cli;
+ enum acl_mode mode = cbstate->mode;
+ struct security_descriptor *sd = NULL;
+ struct security_descriptor *old = get_secdesc_with_ctx(ctx, cli,
+ filename);
+ uint32_t j;
+ bool propagate = false;
+
+ if (!old) {
+ result = NT_STATUS_UNSUCCESSFUL;
+ goto out;
+ }
+
+ /* parse acl passed on the command line */
+ if (sddl) {
+ cbstate->aclsd = sddl_decode(ctx, the_acl,
+ get_global_sam_sid());
+ } else {
+ cbstate->aclsd = sec_desc_parse(ctx, cli, the_acl);
+ }
+
+ if (!cbstate->aclsd) {
+ result = NT_STATUS_UNSUCCESSFUL;
+ goto out;
+ }
+
+ /* need to do inheritance propagation related bits? */
+ if (cbstate->acl_no_propagate) {
+ result = NT_STATUS_OK;
+ goto out;
+ }
+
+ sd = cbstate->aclsd;
+
+ cbstate->inherit_enabled = false;
+ if (!(old->type & SEC_DESC_DACL_PROTECTED)) {
+ cbstate->inherit_enabled = true;
+ }
+ /* set operation if inheritance is enabled doesn't make sense */
+ if (mode == SMB_ACL_SET && cbstate->inherit_enabled) {
+ d_printf("Inheritance enabled at %s, can't apply set operation\n",filename);
+ result = NT_STATUS_UNSUCCESSFUL;
+ goto out;
+ }
+
+ /*
+ * search command line acl for any illegal SEC_ACE_FLAG_INHERITED_ACE
+ * flags that are set
+ */
+ for (j = 0; sd->dacl && j < sd->dacl->num_aces; j++) {
+ struct security_ace *ace = &sd->dacl->aces[j];
+ if (ace->flags & SEC_ACE_FLAG_INHERITED_ACE) {
+ d_printf("Illegal paramater %s\n", the_acl);
+ result = NT_STATUS_UNSUCCESSFUL;
+ goto out;
+ }
+ if (!propagate) {
+ if (is_inheritable_ace(ace, false)) {
+ propagate = true;
+ }
+ }
+ }
+out:
+ cbstate->acl_no_propagate = !propagate;
+ return result;
+}
+
+/*
+ * This method builds inheritable ace(s) from filename (which should be
+ * a container) that need propagating to children in order to provide
+ * automatic inheritance. Those inheritably ace(s) are stored in
+ * acl_to_add member of cbstate for later processing
+ * (see propagate_inherited_aces)
+ */
+static NTSTATUS get_parents_inheritable_aces(TALLOC_CTX *ctx, char *filename,
+ struct cacl_callback_state *cbstate,
+ bool filter_inherited_only)
+{
+ NTSTATUS result = NT_STATUS_OK;
+ struct cli_state *cli = cbstate->cli;
+ struct security_descriptor *sd = get_secdesc_with_ctx(ctx, cli,
+ filename);
+ struct security_acl *acl_to_add = NULL;
+ int j;
+ if (!sd) {
+ result = NT_STATUS_UNSUCCESSFUL;
+ goto out;
+ }
+ /*
+ * Check if any inheritance related flags are used, if not then
+ * nothing to do. At the same time populate acls for inheritance
+ * related ace(s) that need to be added to or deleted from children as
+ * a result of inheritance propagation.
+ */
+
+ for (j = 0; sd->dacl && j < sd->dacl->num_aces; j++) {
+ struct security_ace *ace = &sd->dacl->aces[j];
+ if (is_inheritable_ace(ace, filter_inherited_only)) {
+ bool added = add_ace_with_ctx(ctx, &acl_to_add, ace);
+ if (!added) {
+ result = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+ }
+ cbstate->acl_to_add = acl_to_add;
+out:
+ return result;
+}
+
+/*
+ * Callback handler to handle child elements processed by cli_list, we attempt
+ * to propagate inheritable ace(s) to each child via the function
+ * propagate_inherited_aces. Children that are themselves directories are passed
+ * to cli_list again ( to decend the directory structure )
+ */
+static NTSTATUS cacl_set_cb(const char *mntpoint, struct file_info *f,
+ const char *mask, void *state)
+{
+ struct cacl_callback_state *cbstate =
+ (struct cacl_callback_state *)state;
+ struct cli_state *cli = cbstate->cli;
+ struct user_auth_info *auth_info = cbstate->auth_info;
+
+ TALLOC_CTX *dirctx;
+ NTSTATUS status = NT_STATUS_OK;
+
+ char *dir = NULL;
+ char *dir_end = NULL;
+ char *mask2 = NULL;
+ char *targetpath = NULL;
+ char *caclfile = NULL;
+
+ dirctx = talloc_new(NULL);
+ if (!dirctx) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ /* Work out the directory. */
+ dir = talloc_strdup(dirctx, mask);
+ if (!dir) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ dir_end = strrchr(dir, DIRSEP_CHAR);
+ if (dir_end != NULL) {
+ *dir_end = '\0';
+ }
+
+ if (f->mode & FILE_ATTRIBUTE_DIRECTORY) {
+ struct cli_state *targetcli = NULL;
+ struct cacl_callback_state dir_cbstate;
+ uint16_t attribute = FILE_ATTRIBUTE_DIRECTORY
+ | FILE_ATTRIBUTE_SYSTEM
+ | FILE_ATTRIBUTE_HIDDEN;
+ dir_end = NULL;
+
+ if (!f->name || !f->name[0]) {
+ d_printf("Empty dir name returned. Possible server misconfiguration.\n");
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto out;
+ }
+
+ /* ignore special '.' & '..' */
+ if (!f->name || strequal(f->name, ".") ||
+ strequal(f->name, "..")) {
+ status = NT_STATUS_OK;
+ goto out;
+ }
+
+ mask2 = build_dirname(dirctx, mntpoint, mask, dir, f->name);
+ if (mask2 == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ /* check for dfs */
+ status = cli_resolve_path(dirctx, "", auth_info, cli,
+ mask2, &targetcli, &targetpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ /*
+ * prepare path to caclfile, remove any existing wildcard
+ * chars and convert path separators.
+ */
+
+ caclfile = talloc_strdup(dirctx, targetpath);
+ if (!caclfile) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ dir_end = strrchr(caclfile, '*');
+ if (dir_end != NULL) {
+ *dir_end = '\0';
+ }
+
+ string_replace(caclfile, '/', '\\');
+
+ /* attempt to propagate any inherited ace to child */
+ status = propagate_inherited_aces(caclfile, cbstate);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ /* get acls from parent (e.g. caclfile) */
+ dir_cbstate = *cbstate;
+ status = get_parents_inheritable_aces(dirctx, caclfile,
+ &dir_cbstate, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ /*
+ * ensure cacl_set_cb gets called for children
+ * of the directory (targetpath)
+ */
+ status = cli_list(targetcli, targetpath,
+ attribute, cacl_set_cb,
+ (void *)&dir_cbstate);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ } else {
+ /*
+ * build full path to caclfile and replace '/' with '\' so
+ * other utility functions can deal with it
+ */
+
+ caclfile = talloc_asprintf(dirctx, "%s/%s", dir, f->name);
+ if (!caclfile) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ string_replace(caclfile, '/', '\\');
+
+ /* attempt to propagate any inherited ace to file caclfile */
+ status = propagate_inherited_aces(caclfile, cbstate);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ }
+ status = NT_STATUS_OK;
+out:
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("error %s: processing %s\n",
+ nt_errstr(status),
+ caclfile);
+ }
+ TALLOC_FREE(dirctx);
+ return status;
+}
+
+
+/*
+ * Wrapper around cl_list to (if required) decend the directory tree pointed
+ * to by 'filename', helper callback function 'cacl_set_cb' handles the child
+ * elements processed by cli_list. A smbcacls without '--propagate-inheritance'
+ * won't call cli_list (inhibited by cacl_callback_state.acl_no_propagate
+ */
+static int cacl_set(char *filename,
+ struct cacl_callback_state *cbstate)
+{
+ int result = EXIT_OK;
+ NTSTATUS ntstatus;
+ int fileattr = 0;
+ char *targetpath = NULL;
+ char *mask = NULL;
+ struct cli_state *cli = cbstate->cli;
+ struct cli_state *targetcli = NULL;
+ struct user_auth_info *auth_info = cbstate->auth_info;
+ TALLOC_CTX *ctx;
+ bool isdirectory = false;
+ uint16_t attribute = FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM
+ | FILE_ATTRIBUTE_HIDDEN;
+ ctx = talloc_new(NULL);
+ if (!ctx) {
+ result = EXIT_FAILED;
+ goto out;
+ }
+ /* ensure we have a filename that starts with '\' */
+ if (!filename || *filename != DIRSEP_CHAR) {
+ /* illegal or no filename */
+ result = EXIT_FAILED;
+ d_printf("illegal or missing name '%s'\n", filename);
+ goto out;
+ }
+
+ fileattr = get_fileinfo(cli, filename);
+ isdirectory = (fileattr & FILE_ATTRIBUTE_DIRECTORY)
+ == FILE_ATTRIBUTE_DIRECTORY;
+
+ /*
+ * if we've got as far as here then we have already evaluated
+ * the args.
+ */
+ if (test_args) {
+ goto out;
+ }
+
+ mask = NULL;
+ /* make sure we have a trailing '\*' for directory */
+ if (!isdirectory) {
+ mask = talloc_strdup(ctx, filename);
+ } else if (strlen(filename) > 1) {
+ /*
+ * if the passed file name doesn't have a trailing '\'
+ * append it.
+ */
+ char *name_end = strrchr(filename, DIRSEP_CHAR);
+ if (name_end != filename + strlen(filename) + 1) {
+ mask = talloc_asprintf(ctx, "%s\\*", filename);
+ } else {
+ mask = talloc_strdup(ctx, filename);
+ }
+ } else {
+ /* filename is a single '\', just append '*' */
+ mask = talloc_asprintf_append(mask, "%s*", filename);
+ }
+
+ if (!mask) {
+ result = EXIT_FAILED;
+ goto out;
+ }
+
+ /*
+ * prepare for automatic propagation of the acl passed on the
+ * cmdline.
+ */
+ ntstatus = prepare_inheritance_propagation(ctx, filename,
+ cbstate);
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ goto out;
+ }
+
+ /* check for dfs */
+ ntstatus = cli_resolve_path(ctx, "", auth_info, cli, mask, &targetcli,
+ &targetpath);
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ goto out;
+ }
+
+ /*
+ * it seems that if inheritance isn't enabled then we don't need
+ * to take into account the parent's inheritable inherited ace(s)
+ */
+
+ if (cbstate->inherit_enabled) {
+ /* get only inherited inheritable flags (I) */
+ ntstatus = get_parents_inheritable_aces(ctx, filename,
+ cbstate, true);
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ goto out;
+ }
+ }
+
+ result = cacl_set_from_sd(cli, filename, cbstate->aclsd,
+ cbstate->mode, cbstate->numeric);
+
+ /*
+ * strictly speaking it could be considered an error if a file was
+ * specificied with '--propagate-inheritance'. However we really want
+ * to eventually get rid of '--propagate-inheritance' so we will be
+ * more forgiving here and instead just exit early.
+ */
+ if (!isdirectory || (result != EXIT_OK)) {
+ goto out;
+ }
+
+ /* check if there is actually any need to propagate */
+ if (cbstate->acl_no_propagate) {
+ goto out;
+ }
+
+ /* get inheritable attributes from parent (e.g. filename) */
+ ntstatus = get_parents_inheritable_aces(ctx, filename, cbstate, false);
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ goto out;
+ }
+ /* process children */
+ ntstatus = cli_list(targetcli, targetpath, attribute,
+ cacl_set_cb,
+ (void *)cbstate);
+out:
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ d_printf("error: %s processing %s\n",
+ nt_errstr(ntstatus), targetpath);
+ result = EXIT_FAILED;
+ }
+ TALLOC_FREE(ctx);
+ return result;
+}
+
/****************************************************************************
main program
****************************************************************************/
@@ -802,6 +1455,7 @@ int main(int argc, char *argv[])
{ "chgrp", 'G', POPT_ARG_STRING, NULL, 'G', "Change group ownership of a file", "GROUPNAME" },
{ "inherit", 'I', POPT_ARG_STRING, NULL, 'I', "Inherit allow|remove|copy" },
{ "numeric", 0, POPT_ARG_NONE, &numeric, 1, "Don't resolve sids or masks to names" },
+ { "propagate-inheritance", 0, POPT_ARG_NONE, &inheritance, 1, "Supports propagation of inheritable ACE(s) when used in conjunction with add, delete, set or modify" },
{ "sddl", 0, POPT_ARG_NONE, &sddl, 1, "Output and input acls in sddl format" },
{ "query-security-info", 0, POPT_ARG_INT, &query_sec_info, 1,
"The security-info flags for queries"
@@ -879,8 +1533,11 @@ int main(int argc, char *argv[])
break;
}
}
+ if (inheritance && !the_acl) {
+ poptPrintUsage(pc, stderr, 0);
+ return -1;
+ }
- /* Make connection to server */
if(!poptPeekArg(pc)) {
poptPrintUsage(pc, stderr, 0);
return -1;
@@ -919,6 +1576,7 @@ int main(int argc, char *argv[])
*share = 0;
share++;
+ /* Make connection to server */
if (!test_args) {
cli = connect_one(cmdline_auth_info, server, share);
if (!cli) {
@@ -945,7 +1603,21 @@ int main(int argc, char *argv[])
} else if (change_mode != REQUEST_NONE) {
result = owner_set(cli, change_mode, filename, owner_username);
} else if (the_acl) {
- result = cacl_set(cli, filename, the_acl, mode, numeric);
+ struct cacl_callback_state cbstate;
+ cbstate.auth_info = cmdline_auth_info;
+ cbstate.cli = cli;
+ cbstate.aclsd = NULL;
+ cbstate.acl_to_add = NULL;
+ cbstate.mode = mode;
+ cbstate.the_acl = the_acl;
+ cbstate.inherit_enabled = false;
+ cbstate.numeric = numeric;
+ if (inheritance) {
+ cbstate.acl_no_propagate = false;
+ } else {
+ cbstate.acl_no_propagate = true;
+ }
+ result = cacl_set(filename, &cbstate);
} else {
result = cacl_dump(cli, filename, numeric);
}
--
2.10.2
From 811d410dd281c625a66a6407a6f91a6c1a5ccf1b Mon Sep 17 00:00:00 2001
From: David Disseldorp <ddiss at samba.org>
Date: Thu, 14 Nov 2013 19:38:19 +0100
Subject: [PATCH 3/4] doc: describe smbcacls --propagate-inheritance
Signed-off-by: David Disseldorp <ddiss at samba.org>
---
docs-xml/manpages/smbcacls.1.xml | 51 ++++++++++++++++++++++++++++++----------
1 file changed, 39 insertions(+), 12 deletions(-)
diff --git a/docs-xml/manpages/smbcacls.1.xml b/docs-xml/manpages/smbcacls.1.xml
index cb198a7..00f4778 100644
--- a/docs-xml/manpages/smbcacls.1.xml
+++ b/docs-xml/manpages/smbcacls.1.xml
@@ -28,6 +28,7 @@
<arg choice="opt">-C|--chown name</arg>
<arg choice="opt">-G|--chgrp name</arg>
<arg choice="opt">-I allow|remove|copy</arg>
+ <arg choice="opt">--propagate-inheritance</arg>
<arg choice="opt">--numeric</arg>
<arg choice="opt">-t</arg>
<arg choice="opt">-U username</arg>
@@ -123,19 +124,23 @@
<para>This command is a shortcut for -M GROUP:name.</para></listitem>
</varlistentry>
-
-
<varlistentry>
<term>-I|--inherit allow|remove|copy</term>
<listitem><para>Set or unset the windows "Allow inheritable
permissions" check box using the <parameter>-I</parameter>
option. To set the check box pass allow. To unset the check
box pass either remove or copy. Remove will remove all
- inherited acls. Copy will copy all the inherited acls.
+ inherited ACEs. Copy will copy all the inherited ACEs.
</para></listitem>
-
</varlistentry>
+ <varlistentry>
+ <term>--propagate-inheritance</term>
+ <listitem><para>Add, modify, delete or set ACEs on an entire
+ directory tree according to the inheritance flags. Refer to the
+ INHERITANCE section for details.
+ </para></listitem>
+ </varlistentry>
<varlistentry>
<term>--numeric</term>
@@ -229,18 +234,22 @@ ACL:<sid or name>:<type>/<flags>/<mask>
determine the type of access granted to the SID. </para>
<para>The type can be either ALLOWED or DENIED to allow/deny access
- to the SID. The flags values are generally zero for file ACEs and
- either 9 or 2 for directory ACEs. Some common flags are: </para>
+ to the SID.</para>
+
+ <para>The flags field defines how the ACE should be considered when
+ performing inheritance. <command>smbcacls</command> uses these flags
+ when run with <parameter>--propagate-inheritance</parameter>.</para>
+
+ <para>Flags can be specified as decimal or hexadecimal values, or with
+ the respective (XX) aliases, separated by a vertical bar "|".</para>
<itemizedlist>
- <listitem><para><constant>#define SEC_ACE_FLAG_OBJECT_INHERIT 0x1</constant></para></listitem>
- <listitem><para><constant>#define SEC_ACE_FLAG_CONTAINER_INHERIT 0x2</constant></para></listitem>
- <listitem><para><constant>#define SEC_ACE_FLAG_NO_PROPAGATE_INHERIT 0x4</constant></para></listitem>
- <listitem><para><constant>#define SEC_ACE_FLAG_INHERIT_ONLY 0x8</constant></para></listitem>
+ <listitem><para><emphasis>(OI)</emphasis> Object Inherit 0x1</para></listitem>
+ <listitem><para><emphasis>(CI)</emphasis> Container Inherit 0x2</para></listitem>
+ <listitem><para><emphasis>(NP)</emphasis> No Propagate Inherit 0x4</para></listitem>
+ <listitem><para><emphasis>(IO)</emphasis> Inherit Only 0x8</para></listitem>
</itemizedlist>
- <para>At present, flags can only be specified as decimal or
- hexadecimal values.</para>
<para>The mask is a value which expresses the access right
granted to the SID. It can be given as a decimal or hexadecimal value,
@@ -271,6 +280,24 @@ ACL:<sid or name>:<type>/<flags>/<mask>
</refsect1>
<refsect1>
+ <title>INHERITANCE</title>
+
+ <para>Per-ACE inheritance flags can be set in the ACE flags field. By
+ default, ACEs marked for object inheritance (OI) or container
+ inheritance (CI) are not propagated to sub-files or folders. However,
+ with the <parameter>--propagate-inheritance</parameter> arguement
+ specified, such ACEs are recursively applied to all applicable child
+ objects in the directory tree.</para>
+
+ <para>Any ACEs applied to sub-files of folders are marked with the
+ inherited (I) flag.</para>
+
+ <para>Files and folders with protected ACLs do not allow inheritable
+ permissions (set with <parameter>-I</parameter>). Such objects will
+ not receive ACEs flagged for inheritance with (CI) or (OI).</para>
+</refsect1>
+
+<refsect1>
<title>EXIT STATUS</title>
<para>The <command>smbcacls</command> program sets the exit status
--
2.10.2
From 50d8a51d724be50ffdc0729d54a6b9a0a6eae061 Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Fri, 15 Nov 2013 11:53:35 +0000
Subject: [PATCH 4/4] doc: describe smbcacls --propagate-inheritance expanding
INHERITANCE section
Signed-off-by: Noel Power <noel.power at suse.com>
---
docs-xml/manpages/smbcacls.1.xml | 84 ++++++++++++++++++++++++++++++++++------
1 file changed, 73 insertions(+), 11 deletions(-)
diff --git a/docs-xml/manpages/smbcacls.1.xml b/docs-xml/manpages/smbcacls.1.xml
index 00f4778..95dc5bc 100644
--- a/docs-xml/manpages/smbcacls.1.xml
+++ b/docs-xml/manpages/smbcacls.1.xml
@@ -27,7 +27,7 @@
<arg choice="opt">-S|--set acl</arg>
<arg choice="opt">-C|--chown name</arg>
<arg choice="opt">-G|--chgrp name</arg>
- <arg choice="opt">-I allow|remove|copy</arg>
+ <arg choice="opt">-I|--inherit allow|remove|copy</arg>
<arg choice="opt">--propagate-inheritance</arg>
<arg choice="opt">--numeric</arg>
<arg choice="opt">-t</arg>
@@ -49,7 +49,7 @@
<manvolnum>7</manvolnum></citerefentry> suite.</para>
<para>The <command>smbcacls</command> program manipulates NT Access Control
- Lists (ACLs) on SMB file shares. An ACL is comprised zero or more Access
+ Lists (ACLs) on SMB file shares. An ACL is comprised of zero or more Access
Control Entries (ACEs), which define access restrictions for a specific
user or group.</para>
</refsect1>
@@ -283,18 +283,80 @@ ACL:<sid or name>:<type>/<flags>/<mask>
<title>INHERITANCE</title>
<para>Per-ACE inheritance flags can be set in the ACE flags field. By
- default, ACEs marked for object inheritance (OI) or container
- inheritance (CI) are not propagated to sub-files or folders. However,
- with the <parameter>--propagate-inheritance</parameter> arguement
- specified, such ACEs are recursively applied to all applicable child
- objects in the directory tree.</para>
-
- <para>Any ACEs applied to sub-files of folders are marked with the
- inherited (I) flag.</para>
-
+ default, inheritable ACEs e.g. those marked for object inheritance (OI)
+ or container inheritance (CI), are not propagated to sub-files or
+ folders. However, with the
+ <parameter>--propagate-inheritance</parameter> argument specified, such
+ ACEs are automatically propagated according to some inheritance
+ rules.
+ <itemizedlist>
+ <listitem><para>Inheritable (OI)(CI) ACE flags can only be
+ applied to folders. </para></listitem>
+ <listitem><para>Any inheritable ACEs applied to sub-files or
+ folders are marked with the inherited (I) flag. Inheritable
+ ACE(s) are applied to folders unless the no propagation (NP)
+ flag is set. </para>
+ </listitem>
+ <listitem><para>When an ACE with the (OI) flag alone set is
+ progagated to a child folder the inheritance only flag (IO) is
+ also applied. This indicates the permissions associated with
+ the ACE don't apply to the folder itself (only to it's
+ child files). When applying the ACE to a child file the ACE is
+ inherited as normal.</para></listitem>
+ <listitem><para>When an ace with the (CI) flag alone set is
+ propagated to a child file there is no effect, when propagated
+ to a child folder it is inherited as normal.
+ </para></listitem>
+ <listitem><para>When an ACE that has both (OI) & (CI) flags
+ set the ACE is inherited as normal by both folders and
+ files.</para></listitem>
+ </itemizedlist></para>
+<para>(OI)(READ) added to parent folder</para>
+<para><programlisting>
++-parent/ (OI)(READ)
+| +-file.1 (I)(READ)
+| +-nested/ (OI)(IO)(I)(READ)
+ | +-file.2 (I)(READ)
+</programlisting></para>
+<para>(CI)(READ) added to parent folder</para>
+<para><programlisting>
++-parent/ (CI)(READ)
+| +-file.1
+| +-nested/ (CI)(I)(READ)
+ | +-file.2
+</programlisting></para>
+<para>(OI)(CI)(READ) added to parent folder</para>
+<para><programlisting>
++-parent/ (OI)(CI)(READ)
+| +-file.1 (I)(READ)
+| +-nested/ (OI)(CI)(I)(READ)
+ | +-file.2 (I)(READ)
+</programlisting></para>
+<para>(OI)(NP)(READ) added to parent folder</para>
+<para><programlisting>
++-oi_dir/ (OI)(NP)(READ)
+| +-file.1 (I)(READ)
+| +-nested/
+| +-file.2
+</programlisting></para>
+<para>(CI)(NP)(READ) added to parent folder</para>
+<para><programlisting>
++-oi_dir/ (CI)(NP)(READ)
+| +-file.1
+| +-nested/ (I)(READ)
+| +-file.2
+</programlisting></para>
+<para>(OI)(CI)(NP)(READ) added to parent folder</para>
+<para><programlisting>
++-parent/ (CI)(OI)(NP)(READ)
+| +-file.1 (I)(READ)
+| +-nested/ (I)(READ)
+| +-file.2
+</programlisting></para>
<para>Files and folders with protected ACLs do not allow inheritable
permissions (set with <parameter>-I</parameter>). Such objects will
not receive ACEs flagged for inheritance with (CI) or (OI).</para>
+
</refsect1>
<refsect1>
--
2.10.2
More information about the samba-technical
mailing list