smbcacls support for automatic inheritance propagation

Noel Power nopower at suse.com
Wed Feb 1 17:33:05 UTC 2017


Hi,

Some recent housekeeping brought my attention back to the smbcacls stuff
so I'd like to try and restart this again. The latest patch(s) and tests
(rebased against current master) are located
https://cgit.freedesktop.org/~noelp/noelp-samba/log/?h=smbcacls_review%235 
Note: There is no signoff etc for the top 13 or so patches, these were
patches still unreviewed (from a previous review)

Depending on comments I intend to squash those into the respective
patches for smbcalcs.c and  test_smbcacls.pl

thanks,

Noel

On 03/06/15 09:21, nopower at suse.com (Noel Power) wrote:
> On 02/06/15 18:19, Jeremy Allison wrote:
>> On Fri, Apr 24, 2015 at 11:46:41AM +0100, Noel Power wrote:
>>> Hi Dave, *
>>>
>>> I'd like to try reboot the review of this (rather large) patch, please
>>> see
>>> https://lists.samba.org/archive/samba-technical/2014-April/098883.html
>>> or
>>> http://samba.2283325.n4.nabble.com/smbcacls-support-for-automatic-inheritance-propagation-tt4656249.html#none
>>> for a fuller view of the thread.
>>> Apologies for breaking the thread but mail server here has auto-deleted
>>> my local copy.
>>>
>>> please see the following branch,
>>> http://cgit.freedesktop.org/~noelp/noelp-samba/log/?h=smbcacls_review%234
>>>
>>> It's basically the same branch as mentioned in the last mail in this
>>> thread (dated April 2nd) ported to current master, there are
>>> now also some additional patches on top of that branch (they can be
>>> squashed in a future round if acceptable)
>>>
>>> The relevant patches (to be applied on top of master) are as follows
>>> http://cgit.freedesktop.org/~noelp/noelp-samba/log/?h=smbcacls_review%234&id=5074cf825d046c0523de501e00..4b607db981cb074349992cb40e668389d40266b7cbfb4fbb814149..&qt=range&q=5074cf825d046c0523de501e00cbfb4fbb814149..4b607db981cb074349992cb40e668389d40266b7
>> Noel, can you repost this as a patchset to the list please ?
>>
>> I don't have the time to be grubbung through external git
>> repos, sorry.
> No problem, hopefully this doesn't trigger some size limit problem
>
>
> Noel
> -------------- next part --------------
> A non-text attachment was scrubbed...
> Name: smbcacls.patch
> Type: text/x-diff
> Size: 155932 bytes
> Desc: not available
> URL: <http://lists.samba.org/pipermail/samba-technical/attachments/20150603/04f7af3e/attachment-0001.patch>
>

-------------- next part --------------
>From 5363a7bad92415e4885fe5927d5d12385d95d17c 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 01/17] add smbcacls test based on test_smbclient_tarmode.pl

Pair-Programmed-With: Noel Power <noel.power at suse.com>
---
 selftest/selftesthelpers.py           |    1 +
 source3/script/tests/test_smbcacls.pl | 3061 +++++++++++++++++++++++++++++++++
 source3/selftest/tests.py             |   22 +
 3 files changed, 3084 insertions(+)
 create mode 100644 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 100644
index 0000000..e58d9e5
--- /dev/null
+++ b/source3/script/tests/test_smbcacls.pl
@@ -0,0 +1,3061 @@
+#!/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 $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);
+
+	$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 $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;
+	}
+
+	# overwrite existing entry
+	$acl_str = "ACL:$USER:ALLOWED/0x0/READ";
+	smb_cacls('--modify', $acl_str, $f->smb_remotepath);
+	$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 $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;
+	}
+
+	smb_cacls('--delete', $acl_str, $f->smb_remotepath);
+	$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 $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) {
+		say "missing ace";
+		return 1;
+	}
+
+	smb_cacls('--add', $dny_acl_str, $f->smb_remotepath);
+	$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 $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");
+
+	smb_cacls('--propagate-inheritance', '--add', $dir_add_acl_str,
+		  $f1->smb_remotedir);
+
+	# 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 $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
+	smb_cacls('--add', $dir_acl_str,
+		$f1->smb_remotedir);
+	# add flags on oi_dir/nested
+	smb_cacls('--add', $dir_inherited_ace_str,
+		$f2->smb_remotedir);
+	# add flags on oi_dir/file-1
+	smb_cacls('--add', $obj_inherited_ace_str,
+		$f1->smb_remotepath);
+	# add flags on oi_dir/nested/file-2
+	smb_cacls('--add', $obj_inherited_ace_str,
+		$f2->smb_remotepath);
+
+	smb_cacls('--propagate-inheritance', '--delete', $dir_acl_str,
+		  $f1->smb_remotedir);
+
+	# 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 $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");
+	smb_cacls('--propagate-inheritance', '--add', $dir_add_acl_str,
+		  $f1->smb_remotedir);
+	# 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 $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
+	smb_cacls('--add', $dir_acl_str,
+		$f1->smb_remotedir);
+	# add flags on oi_dir/nested
+	smb_cacls('--add', $dir_inherited_ace_str,
+		$f2->smb_remotedir);
+	# make sure no (I|RX) flags on oi_dir/file-1
+	smb_cacls('--delete', $file_inherited_ace_str,
+		$f1->smb_remotepath);
+	# make sure no (I|RX) flags on oi_dir/nested/file-2
+	smb_cacls('--delete', $file_inherited_ace_str,
+		$f2->smb_remotepath);
+	smb_cacls('--propagate-inheritance', '--delete', $dir_acl_str,
+		  $f1->smb_remotedir);
+
+	# 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 $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");
+
+	smb_cacls('--propagate-inheritance', '--add', $dir_add_acl_str,
+		  $f1->smb_remotedir);
+
+	# 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 $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
+	smb_cacls('--add', $dir_acl_str,
+		$f1->smb_remotedir);
+	# add flags on oi_dir/nested
+	smb_cacls('--add', $dir_inherited_ace_str,
+		$f2->smb_remotedir);
+	# add flags on oi_dir/file-1
+	smb_cacls('--add', $file_inherited_ace_str,
+		$f1->smb_remotepath);
+	# add flags on oi_dir/nested/file-2
+	smb_cacls('--add', $file_inherited_ace_str,
+		$f2->smb_remotepath);
+
+	smb_cacls('--propagate-inheritance', '--delete', $dir_acl_str,
+		  $f1->smb_remotedir);
+
+	# 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)(D)
+#    | +-file.1       (I)(D)
+#    | +-nested/      (CI)(OI)(I)(D)
+#    |   +-file.2     (I)(D)
+
+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";
+	# 0x00110000 = D in icacls
+	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 $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
+
+	smb_cacls('--add', $dir_acl_str,
+		$f1->smb_remotedir);
+	# add flags on oi_dir/nested
+	smb_cacls('--add', $dir_inherited_ace_str,
+		$f2->smb_remotedir);
+	# add flags on oi_dir/file-1
+	smb_cacls('--add', $file_inherited_ace_str,
+		$f1->smb_remotepath);
+	# add flags on oi_dir/nested/file-2
+	smb_cacls('--add', $file_inherited_ace_str,
+		$f2->smb_remotepath);
+	
+	smb_cacls('--propagate-inheritance', '--modify', $dir_mod_acl_str,
+		  $f1->smb_remotedir);
+
+	# check top level container 'oi_dir' has OI|CI/0x00110000
+	$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/0x00110000
+	$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|0x00110000
+	$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)(D)
+#    | +-file.1       (I)(D)
+#    | +-nested/      (OI)(IO)(I)(D)
+#    |   +-file.2     (I)(D)
+sub test_simple_oi_modify
+{
+	my @files;
+	my $dir_acl_str = "ACL:$USER:ALLOWED/OI/READ";
+	my $file_inherited_ace_str = "ACL:$USER:ALLOWED/I/READ";
+	my $dir_inherited_ace_str = "ACL:$USER:ALLOWED/OI|IO|I/READ";
+
+	# 0x00110000 = D in icacls
+	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 $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
+
+	smb_cacls('--add', $dir_acl_str,
+		$f1->smb_remotedir);
+	# add flags on oi_dir/nested
+	smb_cacls('--add', $dir_inherited_ace_str,
+		$f2->smb_remotedir);
+	# add flags on oi_dir/file-1
+	smb_cacls('--add', $file_inherited_ace_str,
+		$f1->smb_remotepath);
+	# add flags on oi_dir/nested/file-2
+	smb_cacls('--add', $file_inherited_ace_str,
+		$f2->smb_remotepath);
+	
+	smb_cacls('--propagate-inheritance', '--modify', $dir_mod_acl_str,
+		  $f1->smb_remotedir);
+
+	# check top level container 'oi_dir' has OI/0x00110000
+	$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/0x00110000
+	$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/0x00110000
+	$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/0x00110000
+	$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)(D)
+#    | +-file.1            (I)(F)
+#    | +-nested/      (CI)(I)(D)
+#    |   +-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";
+
+	# 0x00110000 = D in icacls
+	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 $dir_ace;
+	my $child_file_ace;
+	my $child_dir_ace;
+	my $ret;
+
+	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
+
+	smb_cacls('--add', $dir_acl_str,
+		$f1->smb_remotedir);
+	# add flags on oi_dir/nested
+	smb_cacls('--add', $dir_inherited_ace_str,
+		$f2->smb_remotedir);
+
+        smb_cacls('--propagate-inheritance', '--modify', $dir_mod_acl_str,
+                $f1->smb_remotedir);
+
+	# check top level container 'oi_dir' has CI/0x00110000
+	$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/0x00110000
+	$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|0x00110000
+	$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 --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;
+	}
+
+	smb_cacls('--propagate-inheritance', '--set', $dir_acl_str,
+		  $f1->smb_remotedir);
+
+	# 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;
+	}
+	smb_cacls('--propagate-inheritance', '--set', $dir_acl_str,
+		  $f1->smb_remotedir);
+
+	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 $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");
+
+	smb_cacls('--propagate-inheritance', '--add', $dir_add_acl_str,
+		  $f1->smb_remotedir);
+
+	# 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 $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");
+
+	smb_cacls('--propagate-inheritance', '--add', $dir_add_acl_str,
+		  $f1->smb_remotedir);
+
+	# 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 $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");
+
+	smb_cacls('--propagate-inheritance', '--add', $dir_add_acl_str,
+		  $f1->smb_remotedir);
+
+	# 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 $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
+	smb_cacls('--add', $dir_add_acl_str,
+		  $f1->smb_remotedir);
+	smb_cacls('--add', $inherited_ace_str,
+		  $f1->smb_remotepath);
+	smb_cacls('--add', $inherited_ace_str,
+		  $f2->smb_remotedir);
+
+	smb_cacls('--propagate-inheritance', '--delete', $dir_add_acl_str,
+		  $f1->smb_remotedir);
+
+	# 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 $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'
+	smb_cacls('--add', $dir_add_acl_str,
+		  $f1->smb_remotedir);
+	smb_cacls('--add', $inherited_ace_str,
+		  $f2->smb_remotedir);
+
+	smb_cacls('--propagate-inheritance', '--delete', $dir_add_acl_str,
+		  $f1->smb_remotedir);
+
+	# 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 $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
+	smb_cacls('--add', $dir_add_acl_str,
+		  $f1->smb_remotedir);
+	smb_cacls('--add', $inherited_ace_str,
+		  $f1->smb_remotepath);
+
+	smb_cacls('--propagate-inheritance', '--delete', $dir_add_acl_str,
+		  $f1->smb_remotedir);
+
+	# 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]);
+	} 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 66898dd6843a88ee1ad47e3bdf1b9e20df890899 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 02/17] 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 | 692 +++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 672 insertions(+), 20 deletions(-)

diff --git a/source3/utils/smbcacls.c b/source3/utils/smbcacls.c
index 255ff97..a2425f9 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,17 @@ 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;
+
+
 static NTSTATUS cli_lsa_lookup_domain_sid(struct cli_state *cli,
 					  struct dom_sid *sid)
 {
@@ -123,12 +138,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 +155,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 +273,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(TALLOC_CTX *ctx,
+					       struct cli_state *cli,
+					       const char *filename)
 {
 	uint16_t fnum = (uint16_t)-1;
 	struct security_descriptor *sd;
@@ -284,7 +309,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);
 
@@ -369,7 +394,7 @@ static int cacl_dump(struct cli_state *cli, const char *filename, bool numeric)
 		return EXIT_OK;
 	}
 
-	sd = get_secdesc(cli, filename);
+	sd = get_secdesc(talloc_tos(), cli, filename);
 	if (sd == NULL) {
 		return EXIT_FAILED;
 	}
@@ -403,7 +428,7 @@ static int owner_set(struct cli_state *cli, enum chown_mode change_mode,
 	if (!StringToSid(cli, &sid, new_username))
 		return EXIT_PARSE_ERROR;
 
-	old = get_secdesc(cli, filename);
+	old = get_secdesc(talloc_tos(), cli, filename);
 
 	if (!old) {
 		return EXIT_FAILED;
@@ -485,23 +510,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,
+		    struct security_descriptor *sd,
 		    char *the_acl, 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;
 
@@ -510,7 +530,7 @@ static int cacl_set(struct cli_state *cli, const char *filename,
 		 * Do not fetch old ACL when it will be overwritten
 		 * completely with a new one.
 		 */
-		old = get_secdesc(cli, filename);
+		old = get_secdesc(talloc_tos(), cli, filename);
 
 		if (!old) {
 			return EXIT_FAILED;
@@ -622,7 +642,7 @@ static int inherit(struct cli_state *cli, const char *filename,
 	size_t sd_size;
 	int result = EXIT_OK;
 
-	old = get_secdesc(cli, filename);
+	old = get_secdesc(talloc_tos(), cli, filename);
 
 	if (!old) {
 		return EXIT_FAILED;
@@ -642,11 +662,11 @@ static int inherit(struct cli_state *cli, const char *filename,
 
 			/* look at parent and copy in all its inheritable ACL's. */
 			string_replace(temp, '\\', '/');
-			if (!parent_dirname(talloc_tos(),temp,&parentname,NULL)) {
+			if (!parent_dirname(talloc_tos(),temp,&parentname,NULL)) 			{
 				return EXIT_FAILED;
 			}
 			string_replace(parentname, '/', '\\');
-			parent = get_secdesc(cli,parentname);
+			parent = get_secdesc(talloc_tos(), cli,parentname);
 			if (parent == NULL) {
 				return EXIT_FAILED;
 			}
@@ -773,6 +793,620 @@ 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 = talloc_new(NULL);
+	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;
+
+	old = get_secdesc(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);
+	if (result  != EXIT_OK) {
+		status = NT_STATUS_UNSUCCESSFUL;
+		goto out;
+	}
+
+	TALLOC_FREE(aclctx);
+out:
+	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(ctx, cli, filename);
+	uint32 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(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 = talloc_new(NULL);
+	NTSTATUS status = NT_STATUS_OK;
+
+	char *dir = NULL;
+	char *dir_end = NULL;
+	char *mask2 = NULL;
+	char *targetpath = NULL;
+	char *caclfile = NULL;
+
+	/* 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;
+		dir_end = NULL;
+		uint16 attribute = FILE_ATTRIBUTE_DIRECTORY
+			| FILE_ATTRIBUTE_SYSTEM
+			| FILE_ATTRIBUTE_HIDDEN;
+
+		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 = talloc_new(NULL);
+	bool isdirectory = false;
+	uint16 attribute = FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM
+				| FILE_ATTRIBUTE_HIDDEN;
+	/* 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);
+
+	/*
+	 * 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;
+	}
+
+	ntstatus = propagate_inherited_aces(filename, cbstate);
+	if (NT_STATUS_IS_OK(ntstatus)) {
+		/* 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 +1436,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 +1514,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 +1557,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,6 +1584,19 @@ 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) {
+		struct cacl_callback_state cbstate;
+		cbstate.auth_info = 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;
+		if (inheritance) {
+			cbstate.acl_no_propagate = false;
+		} else {
+			cbstate.acl_no_propagate = true;
+		}
 		result = cacl_set(cli, filename, the_acl, mode, numeric);
 	} else {
 		result = cacl_dump(cli, filename, numeric);
-- 
2.10.2


>From 2d950d5d1c05ab9c4a5637cb576c071897354b35 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 03/17] 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 d6f4519a90005aa80b649834089f762600a935bd 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 04/17] 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


>From 83cb63daad5d16ac03f5a4184233224df603460e Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Wed, 22 Apr 2015 10:35:54 +0100
Subject: [PATCH 05/17] fix "mixed declarations and code" warning as error

---
 source3/utils/smbcacls.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/source3/utils/smbcacls.c b/source3/utils/smbcacls.c
index a2425f9..7b059fb 100644
--- a/source3/utils/smbcacls.c
+++ b/source3/utils/smbcacls.c
@@ -1162,10 +1162,10 @@ static NTSTATUS cacl_set_cb(const char *mntpoint, struct file_info *f,
 	if (f->mode & FILE_ATTRIBUTE_DIRECTORY) {
 		struct cli_state *targetcli = NULL;
 		struct cacl_callback_state dir_cbstate;
-		dir_end = NULL;
-		uint16 attribute = FILE_ATTRIBUTE_DIRECTORY
+		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");
-- 
2.10.2


>From 86e57106418d4693f8b459e6f16e688170d084b1 Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Wed, 22 Apr 2015 09:35:18 +0100
Subject: [PATCH 06/17] Adjust test to cater for presense of winbind separator
 = '/'

st/client/client.conf now contains the above as default, test was
assuming the seperator between 'domain' & 'user' was '\'

Signed-off-by: Noel Power <noel.power at suse.com>
---
 source3/script/tests/test_smbcacls.pl | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/source3/script/tests/test_smbcacls.pl b/source3/script/tests/test_smbcacls.pl
index e58d9e5..c61a46f 100644
--- a/source3/script/tests/test_smbcacls.pl
+++ b/source3/script/tests/test_smbcacls.pl
@@ -2295,6 +2295,8 @@ sub ace_parse_str
 	#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];
 	}
-- 
2.10.2


>From 6cc985cabf779e46310b9f13b8392cd12d8b3384 Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Thu, 14 Nov 2013 17:28:21 +0000
Subject: [PATCH 07/17] executable permissions on script

Signed-off-by: Noel Power <noel.power at suse.com>
---
 source3/script/tests/test_smbcacls.pl | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 mode change 100644 => 100755 source3/script/tests/test_smbcacls.pl

diff --git a/source3/script/tests/test_smbcacls.pl b/source3/script/tests/test_smbcacls.pl
old mode 100644
new mode 100755
-- 
2.10.2


>From 035e81668d0d1cfd1f99f621c079efea79458fa9 Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Wed, 22 Apr 2015 17:21:53 +0100
Subject: [PATCH 08/17] ensure result from propagate_inherited_aces is handled

---
 source3/utils/smbcacls.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/source3/utils/smbcacls.c b/source3/utils/smbcacls.c
index 7b059fb..d03b9be 100644
--- a/source3/utils/smbcacls.c
+++ b/source3/utils/smbcacls.c
@@ -1389,9 +1389,9 @@ static int cacl_set(char *filename,
 		/* 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;
-		}
+	}
+	if (!NT_STATUS_IS_OK(ntstatus)) {
+		goto out;
 	}
 	/* process children */
 	ntstatus = cli_list(targetcli, targetpath, attribute,
-- 
2.10.2


>From 5c6c4b9b73c3f48ed0341dbdbc74970bfe036a3e Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Thu, 23 Apr 2015 13:17:16 +0100
Subject: [PATCH 09/17] get rid of unecesary call to propagate_inherited_aces
 call

In cacl_set we have already set the security descriptor for the
smbcalcs target dir. Calling propagate_inherited_aces sets the
security descriptor again (and can cause problems e.g. failures where
the new security descriptor from first set prevents the same security
descriptor from being applied again)

Note: this propagate_inherited_aces call is not needed, it's purpose is
to filter out inheritable aces and additionally propagate inheritable
aces. For the 'root directory though even with the
--propagate-inheritance option set the ace(s) are applied with the
(add/set/modify/delete) mode as normal, it's the children that need
to be concerned with what inheritable ace(s) they should receive in
addition to any explicit aces(s) they might have

Signed-off-by: Noel Power <noel.power at suse.com>
---
 source3/utils/smbcacls.c | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/source3/utils/smbcacls.c b/source3/utils/smbcacls.c
index d03b9be..196dadd 100644
--- a/source3/utils/smbcacls.c
+++ b/source3/utils/smbcacls.c
@@ -1384,12 +1384,8 @@ static int cacl_set(char *filename,
 		goto out;
 	}
 
-	ntstatus = propagate_inherited_aces(filename, cbstate);
-	if (NT_STATUS_IS_OK(ntstatus)) {
-		/* get inheritable attributes from parent (e.g. filename) */
-		ntstatus = get_parents_inheritable_aces(ctx, filename,
-							cbstate, false);
-	}
+	/* 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;
 	}
-- 
2.10.2


>From 915056c6f930cd04b3fcf364b86a05379e8530af Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Thu, 23 Apr 2015 13:35:32 +0100
Subject: [PATCH 10/17] check talloc_new for failures

Signed-off-by: Noel Power <noel.power at suse.com>
---
 source3/utils/smbcacls.c | 21 ++++++++++++++++++---
 1 file changed, 18 insertions(+), 3 deletions(-)

diff --git a/source3/utils/smbcacls.c b/source3/utils/smbcacls.c
index 196dadd..b09fba4 100644
--- a/source3/utils/smbcacls.c
+++ b/source3/utils/smbcacls.c
@@ -894,7 +894,7 @@ static uint8_t get_flags_to_propagate(bool is_container,
 static NTSTATUS propagate_inherited_aces(char *caclfile,
 			struct cacl_callback_state *cbstate)
 {
-	TALLOC_CTX *aclctx = talloc_new(NULL);
+	TALLOC_CTX *aclctx;
 	NTSTATUS status = NT_STATUS_OK;
 	int result;
 	int fileattr = 0;
@@ -904,6 +904,11 @@ static NTSTATUS propagate_inherited_aces(char *caclfile,
 	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(aclctx, cbstate->cli, caclfile);
 
 	if (!old) {
@@ -1138,7 +1143,7 @@ static NTSTATUS cacl_set_cb(const char *mntpoint, struct file_info *f,
 	struct cli_state *cli = cbstate->cli;
 	struct user_auth_info *auth_info = cbstate->auth_info;
 
-	TALLOC_CTX *dirctx = talloc_new(NULL);
+	TALLOC_CTX *dirctx;
 	NTSTATUS status = NT_STATUS_OK;
 
 	char *dir = NULL;
@@ -1147,6 +1152,11 @@ static NTSTATUS cacl_set_cb(const char *mntpoint, struct file_info *f,
 	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) {
@@ -1286,10 +1296,15 @@ static int cacl_set(char *filename,
 	struct cli_state *cli = cbstate->cli;
 	struct cli_state *targetcli = NULL;
 	struct user_auth_info *auth_info = cbstate->auth_info;
-	TALLOC_CTX *ctx = talloc_new(NULL);
+	TALLOC_CTX *ctx;
 	bool isdirectory = false;
 	uint16 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 */
-- 
2.10.2


>From 5c9242668c38ff10a983c4de4b80f81ea8c2b700 Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Thu, 23 Apr 2015 13:36:40 +0100
Subject: [PATCH 11/17] free talloc ctx on early exit

Signed-off-by: Noel Power <noel.power at suse.com>
---
 source3/utils/smbcacls.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/source3/utils/smbcacls.c b/source3/utils/smbcacls.c
index b09fba4..f0cf9d9 100644
--- a/source3/utils/smbcacls.c
+++ b/source3/utils/smbcacls.c
@@ -986,8 +986,8 @@ static NTSTATUS propagate_inherited_aces(char *caclfile,
 		goto out;
 	}
 
-	TALLOC_FREE(aclctx);
 out:
+	TALLOC_FREE(aclctx);
 	return status;
 }
 
-- 
2.10.2


>From 8a368e7f28e25498e552bb932f769230ebd304e4 Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Thu, 23 Apr 2015 18:13:15 +0100
Subject: [PATCH 12/17] make sure test_simple_ci_modify resets permissions
 correctly for cleanup

Signed-off-by: Noel Power <noel.power at suse.com>
---
 source3/script/tests/test_smbcacls.pl | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/source3/script/tests/test_smbcacls.pl b/source3/script/tests/test_smbcacls.pl
index c61a46f..aed6bb4 100755
--- a/source3/script/tests/test_smbcacls.pl
+++ b/source3/script/tests/test_smbcacls.pl
@@ -1114,6 +1114,7 @@ sub test_simple_ci_modify
 	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;
@@ -1166,6 +1167,9 @@ sub test_simple_ci_modify
 		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);
 
-- 
2.10.2


>From 06508151104aa309ad7d2efcb2e455d00cac6f57 Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Thu, 23 Apr 2015 19:02:05 +0100
Subject: [PATCH 13/17] check return of smb_calc binary

Signed-off-by: Noel Power <noel.power at suse.com>
---
 source3/script/tests/test_smbcacls.pl | 368 ++++++++++++++++++++++++----------
 1 file changed, 265 insertions(+), 103 deletions(-)

diff --git a/source3/script/tests/test_smbcacls.pl b/source3/script/tests/test_smbcacls.pl
index aed6bb4..595d536 100755
--- a/source3/script/tests/test_smbcacls.pl
+++ b/source3/script/tests/test_smbcacls.pl
@@ -260,12 +260,15 @@ sub test_simple_single_set
 	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);
-
+	($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);
@@ -294,12 +297,15 @@ sub test_simple_single_mod
 	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);
-
+	($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);
@@ -309,7 +315,10 @@ sub test_simple_single_mod
 
 	# overwrite existing entry
 	$acl_str = "ACL:$USER:ALLOWED/0x0/READ";
-	smb_cacls('--modify', $acl_str, $f->smb_remotepath);
+	($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);
 
@@ -336,7 +345,7 @@ sub test_simple_single_del
 	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");
 
@@ -349,7 +358,10 @@ sub test_simple_single_del
 		return 1;
 	}
 
-	smb_cacls('--delete', $acl_str, $f->smb_remotepath);
+	($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) {
@@ -381,12 +393,15 @@ sub test_simple_single_add
 	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");
 
-	smb_cacls('--set', $acl_str, $f->smb_remotepath);
-
+	($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);
@@ -395,7 +410,10 @@ sub test_simple_single_add
 		return 1;
 	}
 
-	smb_cacls('--add', $dny_acl_str, $f->smb_remotepath);
+	($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) {
@@ -442,14 +460,18 @@ sub test_simple_oi_add
 	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");
 
-	smb_cacls('--propagate-inheritance', '--add', $dir_add_acl_str,
-		  $f1->smb_remotedir);
+	($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);
@@ -519,6 +541,7 @@ sub test_simple_oi_delete
 	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");
@@ -526,20 +549,34 @@ sub test_simple_oi_delete
 	$f2->put_remote("oi_dir/nested/file-2");
 
 	# add flags on oi_dir
-	smb_cacls('--add', $dir_acl_str,
-		$f1->smb_remotedir);
+	($out, $ret) = smb_cacls('--add', $dir_acl_str, $f1->smb_remotedir);
+	if ($ret != 0 ) {
+		return 1;
+	}
 	# add flags on oi_dir/nested
-	smb_cacls('--add', $dir_inherited_ace_str,
-		$f2->smb_remotedir);
+	($out, $ret) = smb_cacls('--add', $dir_inherited_ace_str,
+				 $f2->smb_remotedir);
+	if ($ret != 0 ) {
+		return 1;
+	}
 	# add flags on oi_dir/file-1
-	smb_cacls('--add', $obj_inherited_ace_str,
+	($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
-	smb_cacls('--add', $obj_inherited_ace_str,
-		$f2->smb_remotepath);
-
-	smb_cacls('--propagate-inheritance', '--delete', $dir_acl_str,
+	($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);
@@ -608,13 +645,17 @@ sub test_simple_ci_add
 	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");
-	smb_cacls('--propagate-inheritance', '--add', $dir_add_acl_str,
-		  $f1->smb_remotedir);
+	($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);
@@ -676,6 +717,7 @@ sub test_simple_ci_delete
 	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");
@@ -683,20 +725,34 @@ sub test_simple_ci_delete
 	$f2->put_remote("oi_dir/nested/file-2");
 
 	# add flags on oi_dir
-	smb_cacls('--add', $dir_acl_str,
-		$f1->smb_remotedir);
+	($out, $ret) = smb_cacls('--add', $dir_acl_str, $f1->smb_remotedir);
+	if ($ret != 0 ) {
+		return 1;
+	}
 	# add flags on oi_dir/nested
-	smb_cacls('--add', $dir_inherited_ace_str,
-		$f2->smb_remotedir);
+	($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
-	smb_cacls('--delete', $file_inherited_ace_str,
-		$f1->smb_remotepath);
+	($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
-	smb_cacls('--delete', $file_inherited_ace_str,
-		$f2->smb_remotepath);
-	smb_cacls('--propagate-inheritance', '--delete', $dir_acl_str,
-		  $f1->smb_remotedir);
+	($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);
@@ -759,15 +815,18 @@ sub test_simple_cioi_add
 	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");
 
-	smb_cacls('--propagate-inheritance', '--add', $dir_add_acl_str,
-		  $f1->smb_remotedir);
-
+	($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);
@@ -832,6 +891,7 @@ sub test_simple_cioi_delete
 	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");
@@ -839,20 +899,35 @@ sub test_simple_cioi_delete
 	$f2->put_remote("oi_dir/nested/file-2");
 
 	# add flags on oi_dir
-	smb_cacls('--add', $dir_acl_str,
-		$f1->smb_remotedir);
+	($out, $ret) = smb_cacls('--add', $dir_acl_str, $f1->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
 	# add flags on oi_dir/nested
-	smb_cacls('--add', $dir_inherited_ace_str,
-		$f2->smb_remotedir);
+	($out, $ret) = smb_cacls('--add', $dir_inherited_ace_str,
+				 $f2->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
 	# add flags on oi_dir/file-1
-	smb_cacls('--add', $file_inherited_ace_str,
-		$f1->smb_remotepath);
+	($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
-	smb_cacls('--add', $file_inherited_ace_str,
-		$f2->smb_remotepath);
+	($out, $ret) = smb_cacls('--add', $file_inherited_ace_str,
+				 $f2->smb_remotepath);
+	if ($ret != 0) {
+		return 1;
+	}
 
-	smb_cacls('--propagate-inheritance', '--delete', $dir_acl_str,
+	($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);
@@ -922,6 +997,7 @@ sub test_simple_cioi_modify
 	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");
@@ -937,20 +1013,35 @@ sub test_simple_cioi_modify
 	# Note: when running this test against a windows server it seems that
 	# running as Administrator ensures best results
 
-	smb_cacls('--add', $dir_acl_str,
-		$f1->smb_remotedir);
+	($out, $ret) = smb_cacls('--add', $dir_acl_str, $f1->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
 	# add flags on oi_dir/nested
-	smb_cacls('--add', $dir_inherited_ace_str,
-		$f2->smb_remotedir);
+	($out, $ret) = smb_cacls('--add', $dir_inherited_ace_str,
+				 $f2->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
 	# add flags on oi_dir/file-1
-	smb_cacls('--add', $file_inherited_ace_str,
-		$f1->smb_remotepath);
+	($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
-	smb_cacls('--add', $file_inherited_ace_str,
-		$f2->smb_remotepath);
+	($out, $ret) = smb_cacls('--add', $file_inherited_ace_str,
+				 $f2->smb_remotepath);
+	if ($ret != 0) {
+		return 1;
+	}
 	
-	smb_cacls('--propagate-inheritance', '--modify', $dir_mod_acl_str,
+	($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/0x00110000
 	$dir_ace = ace_parse_str($dir_mod_acl_str);
@@ -1017,6 +1108,7 @@ sub test_simple_oi_modify
 	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");
@@ -1032,20 +1124,33 @@ sub test_simple_oi_modify
 	# Note: when running this test against a windows server it seems that
 	# running as Administrator ensures best results
 
-	smb_cacls('--add', $dir_acl_str,
-		$f1->smb_remotedir);
+	($out, $ret) = smb_cacls('--add', $dir_acl_str, $f1->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
 	# add flags on oi_dir/nested
-	smb_cacls('--add', $dir_inherited_ace_str,
-		$f2->smb_remotedir);
+	($out, $ret) = smb_cacls('--add', $dir_inherited_ace_str,
+				 $f2->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
 	# add flags on oi_dir/file-1
-	smb_cacls('--add', $file_inherited_ace_str,
-		$f1->smb_remotepath);
+	($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
-	smb_cacls('--add', $file_inherited_ace_str,
-		$f2->smb_remotepath);
-	
-	smb_cacls('--propagate-inheritance', '--modify', $dir_mod_acl_str,
-		  $f1->smb_remotedir);
+	($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/0x00110000
 	$dir_ace = ace_parse_str($dir_mod_acl_str);
@@ -1120,6 +1225,7 @@ sub test_simple_ci_modify
 	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");
@@ -1135,14 +1241,22 @@ sub test_simple_ci_modify
 	# Note: when running this test against a windows server it seems that
 	# running as Administrator ensures best results
 
-	smb_cacls('--add', $dir_acl_str,
-		$f1->smb_remotedir);
+	($out, $ret) = smb_cacls('--add', $dir_acl_str, $f1->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
 	# add flags on oi_dir/nested
-	smb_cacls('--add', $dir_inherited_ace_str,
-		$f2->smb_remotedir);
+	($out, $ret) = smb_cacls('--add', $dir_inherited_ace_str,
+				 $f2->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
 
-        smb_cacls('--propagate-inheritance', '--modify', $dir_mod_acl_str,
-                $f1->smb_remotedir);
+	($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/0x00110000
 	$dir_ace = ace_parse_str($dir_mod_acl_str);
@@ -1210,8 +1324,8 @@ sub test_simple_set_fail()
 	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);
+	($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";	
@@ -1270,9 +1384,11 @@ sub test_simple_oici_set()
 		return 1;
 	}
 
-	smb_cacls('--propagate-inheritance', '--set', $dir_acl_str,
-		  $f1->smb_remotedir);
-
+	($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);
@@ -1345,8 +1461,11 @@ sub test_simple_ci_set()
 		say "failed to remove inheritance enabled";	
 		return 1;
 	}
-	smb_cacls('--propagate-inheritance', '--set', $dir_acl_str,
-		  $f1->smb_remotedir);
+	($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);
@@ -1412,14 +1531,18 @@ sub test_simple_cioinp_add
 	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");
 
-	smb_cacls('--propagate-inheritance', '--add', $dir_add_acl_str,
-		  $f1->smb_remotedir);
+	($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);
@@ -1490,14 +1613,18 @@ sub test_simple_oinp_add
 	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");
 
-	smb_cacls('--propagate-inheritance', '--add', $dir_add_acl_str,
-		  $f1->smb_remotedir);
+	($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);
@@ -1568,14 +1695,18 @@ sub test_simple_cinp_add
 	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");
 
-	smb_cacls('--propagate-inheritance', '--add', $dir_add_acl_str,
-		  $f1->smb_remotedir);
+	($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);
@@ -1646,6 +1777,7 @@ sub test_simple_cioinp_delete
 	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");
@@ -1653,15 +1785,27 @@ sub test_simple_cioinp_delete
 	$f2->put_remote("oi_dir/nested/file-2");
 
 	# set up 'before' permissions
-	smb_cacls('--add', $dir_add_acl_str,
-		  $f1->smb_remotedir);
-	smb_cacls('--add', $inherited_ace_str,
-		  $f1->smb_remotepath);
-	smb_cacls('--add', $inherited_ace_str,
-		  $f2->smb_remotedir);
+	($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;
+	}
 
-	smb_cacls('--propagate-inheritance', '--delete', $dir_add_acl_str,
+	($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);
@@ -1726,6 +1870,7 @@ sub test_simple_cinp_delete
 	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");
@@ -1733,13 +1878,21 @@ sub test_simple_cinp_delete
 	$f2->put_remote("oi_dir/nested/file-2");
 
 	# set up ace(s) to match 'before'
-	smb_cacls('--add', $dir_add_acl_str,
-		  $f1->smb_remotedir);
-	smb_cacls('--add', $inherited_ace_str,
-		  $f2->smb_remotedir);
+	($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;
+	}
 
-	smb_cacls('--propagate-inheritance', '--delete', $dir_add_acl_str,
-		  $f1->smb_remotedir);
+	($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);
@@ -1812,6 +1965,7 @@ sub test_simple_oinp_delete
 	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");
@@ -1819,13 +1973,21 @@ sub test_simple_oinp_delete
 	$f2->put_remote("oi_dir/nested/file-2");
 
 	# set up 'before' permissions
-	smb_cacls('--add', $dir_add_acl_str,
-		  $f1->smb_remotedir);
-	smb_cacls('--add', $inherited_ace_str,
-		  $f1->smb_remotepath);
+	($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;
+	}
 
-	smb_cacls('--propagate-inheritance', '--delete', $dir_add_acl_str,
-		  $f1->smb_remotedir);
+	($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);
-- 
2.10.2


>From 6473ef9f8ec18b52e2cc7281d6aec3b551d42838 Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Thu, 23 Apr 2015 19:06:01 +0100
Subject: [PATCH 14/17] correct some comments and text

Signed-off-by: Noel Power <noel.power at suse.com>
---
 source3/script/tests/test_smbcacls.pl | 45 +++++++++++++++++------------------
 1 file changed, 22 insertions(+), 23 deletions(-)

diff --git a/source3/script/tests/test_smbcacls.pl b/source3/script/tests/test_smbcacls.pl
index 595d536..b378729 100755
--- a/source3/script/tests/test_smbcacls.pl
+++ b/source3/script/tests/test_smbcacls.pl
@@ -977,10 +977,10 @@ sub test_simple_cioi_delete
 # after/expected:
 #
 #  +-tar_test_dir/    (01)(CI)(I)(F)
-#    +-oi_dir/        (CI)(OI)(D)
-#    | +-file.1       (I)(D)
-#    | +-nested/      (CI)(OI)(I)(D)
-#    |   +-file.2     (I)(D)
+#    +-oi_dir/        (CI)(OI)(CHANGE)
+#    | +-file.1       (I)(CHANGE)
+#    | +-nested/      (CI)(OI)(I)(CHANGE)
+#    |   +-file.2     (I)(CHANGE)
 
 sub test_simple_cioi_modify
 {
@@ -988,7 +988,7 @@ sub test_simple_cioi_modify
 	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";
-	# 0x00110000 = D in icacls
+
 	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";
@@ -1043,7 +1043,7 @@ sub test_simple_cioi_modify
 		return 1;
 	}
 
-	# check top level container 'oi_dir' has OI|CI/0x00110000
+	# 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) {
@@ -1051,14 +1051,14 @@ sub test_simple_cioi_modify
 	}
 
 	$child_file_ace = ace_parse_str($file_mod_inherited_ace_str);
-	# nested file 'oi_dir/file-1' should have inherited I/0x00110000
+	# 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|0x00110000
+	# 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) {
@@ -1088,10 +1088,10 @@ sub test_simple_cioi_modify
 # after/expected:
 #
 #  +-tar_test_dir/    (01)(CI)(I)(F)
-#    +-oi_dir/        (OI)(IO)(D)
-#    | +-file.1       (I)(D)
-#    | +-nested/      (OI)(IO)(I)(D)
-#    |   +-file.2     (I)(D)
+#    +-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;
@@ -1099,7 +1099,7 @@ sub test_simple_oi_modify
 	my $file_inherited_ace_str = "ACL:$USER:ALLOWED/I/READ";
 	my $dir_inherited_ace_str = "ACL:$USER:ALLOWED/OI|IO|I/READ";
 
-	# 0x00110000 = D in icacls
+
 	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";
@@ -1152,13 +1152,13 @@ sub test_simple_oi_modify
 		return 1;
 	}
 
-	# check top level container 'oi_dir' has OI/0x00110000
+	# 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/0x00110000
+	# 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);
 
@@ -1166,14 +1166,14 @@ sub test_simple_oi_modify
 		return 1;
 	}
 
-	# nested dir  'oi_dir/nested/' should have OI|IO/0x00110000
+	# 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/0x00110000
+	# 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";
@@ -1203,9 +1203,9 @@ sub test_simple_oi_modify
 # after/expected:
 #
 #  +-tar_test_dir/    (01)(CI)(I)(F)
-#    +-oi_dir/        (CI)(D)
+#    +-oi_dir/        (CI)(CHANGE)
 #    | +-file.1            (I)(F)
-#    | +-nested/      (CI)(I)(D)
+#    | +-nested/      (CI)(I)(CHANGE)
 #    |   +-file.2          (I)(F)
 
 sub test_simple_ci_modify
@@ -1215,7 +1215,6 @@ sub test_simple_ci_modify
 	my $file_inherited_ace_str = "ACL:$USER:ALLOWED/I/READ";
 	my $dir_inherited_ace_str = "ACL:$USER:ALLOWED/CI|I/READ";
 
-	# 0x00110000 = D in icacls
 	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";
@@ -1258,7 +1257,7 @@ sub test_simple_ci_modify
 		return 1;
 	}
 
-	# check top level container 'oi_dir' has CI/0x00110000
+	# 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) {
@@ -1267,14 +1266,14 @@ sub test_simple_ci_modify
 
 
 	$child_file_ace = ace_parse_str($file_mod_inherited_ace_str);
-	# nested file 'oi_dir/file-1' should NOT have inherited I/0x00110000
+	# 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|0x00110000
+	# 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) {
-- 
2.10.2


>From 098f94b0c22dc03d1fa160ceed27d895db18d845 Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Fri, 24 Apr 2015 12:39:20 +0100
Subject: [PATCH 15/17] ensure windows test can succeed without running as
 Administrator

test_simple_oi_modify fails on windows with access errors, running
as administrator doesn't see to have this issue. The test runs fine
on linux because it seems by default the linux share had already
explict 'FULL' permission set for the user. We mimic this here to
ensure test success for windows also.
---
 source3/script/tests/test_smbcacls.pl | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/source3/script/tests/test_smbcacls.pl b/source3/script/tests/test_smbcacls.pl
index b378729..41bbc0b 100755
--- a/source3/script/tests/test_smbcacls.pl
+++ b/source3/script/tests/test_smbcacls.pl
@@ -1095,9 +1095,7 @@ sub test_simple_cioi_modify
 sub test_simple_oi_modify
 {
 	my @files;
-	my $dir_acl_str = "ACL:$USER:ALLOWED/OI/READ";
-	my $file_inherited_ace_str = "ACL:$USER:ALLOWED/I/READ";
-	my $dir_inherited_ace_str = "ACL:$USER:ALLOWED/OI|IO|I/READ";
+	my $explict_access_ace_str = "ACL:$USER:ALLOWED/0x0/RWD";
 
 
 	my $dir_mod_acl_str = "ACL:$USER:ALLOWED/OI/CHANGE";
@@ -1124,24 +1122,25 @@ sub test_simple_oi_modify
 	# 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);
+	($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', $dir_inherited_ace_str,
+	($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', $file_inherited_ace_str,
+	($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', $file_inherited_ace_str,
+	($out, $ret) = smb_cacls('--add', $explict_access_ace_str,
 				 $f2->smb_remotepath);
 	if ($ret != 0) {
 		return 1;
-- 
2.10.2


>From e5340270dc7f316021ba6261ec75434ddbb4dbce Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Mon, 30 Jan 2017 19:08:12 +0000
Subject: [PATCH 16/17] get working after port

---
 source3/utils/smbcacls.c | 22 ++++++++++++----------
 1 file changed, 12 insertions(+), 10 deletions(-)

diff --git a/source3/utils/smbcacls.c b/source3/utils/smbcacls.c
index f0cf9d9..e9eccec 100644
--- a/source3/utils/smbcacls.c
+++ b/source3/utils/smbcacls.c
@@ -59,8 +59,9 @@ struct cacl_callback_state {
 	enum acl_mode mode;
 	char *the_acl;
 	bool acl_no_propagate;
-};	bool inherit_enabled;
-
+	bool inherit_enabled;
+	bool numeric;
+};
 
 static NTSTATUS cli_lsa_lookup_domain_sid(struct cli_state *cli,
 					  struct dom_sid *sid)
@@ -513,9 +514,9 @@ static void sort_acl(struct security_acl *the_acl)
 set the ACLs on a file given a security descriptor
 *******************************************************/
 
-static int cacl_set(struct cli_state *cli, const char *filename,
+static int cacl_set_from_sd(struct cli_state *cli, const char *filename,
 		    struct security_descriptor *sd,
-		    char *the_acl, enum acl_mode mode, bool numeric)
+		    enum acl_mode mode, bool numeric)
 {
 	struct security_descriptor *old;
 	uint32_t i, j;
@@ -980,7 +981,7 @@ static NTSTATUS propagate_inherited_aces(char *caclfile,
 
 	result = cacl_set_from_sd(cbstate->cli, caclfile,
 				  old,
-				  SMB_ACL_SET);
+				  SMB_ACL_SET, cbstate->numeric);
 	if (result  != EXIT_OK) {
 		status = NT_STATUS_UNSUCCESSFUL;
 		goto out;
@@ -1025,7 +1026,7 @@ static NTSTATUS prepare_inheritance_propagation(TALLOC_CTX *ctx, char *filename,
 	enum acl_mode mode = cbstate->mode;
 	struct security_descriptor *sd = NULL;
 	struct security_descriptor *old = get_secdesc(ctx, cli, filename);
-	uint32 j;
+	uint32_t j;
 	bool propagate = false;
 
 	if (!old) {
@@ -1298,7 +1299,7 @@ static int cacl_set(char *filename,
 	struct user_auth_info *auth_info = cbstate->auth_info;
 	TALLOC_CTX *ctx;
 	bool isdirectory = false;
-	uint16 attribute = FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM
+	uint16_t attribute = FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM
 				| FILE_ATTRIBUTE_HIDDEN;
 	ctx = talloc_new(NULL);
 	if (!ctx) {
@@ -1382,7 +1383,7 @@ static int cacl_set(char *filename,
 	}
 
 	result = cacl_set_from_sd(cli, filename, cbstate->aclsd,
-				cbstate->mode);
+				cbstate->mode, cbstate->numeric);
 
 	/*
 	 * strictly speaking it could be considered an error if a file was
@@ -1596,19 +1597,20 @@ int main(int argc, char *argv[])
 		result = owner_set(cli, change_mode, filename, owner_username);
 	} else if (the_acl) {
 		struct cacl_callback_state cbstate;
-		cbstate.auth_info = auth_info;
+		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(cli, filename, the_acl, mode, numeric);
+		result = cacl_set(filename, &cbstate);
 	} else {
 		result = cacl_dump(cli, filename, numeric);
 	}
-- 
2.10.2


>From f8de89ca7145608befd7754ed519f6373e3700b5 Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Wed, 1 Feb 2017 16:49:51 +0000
Subject: [PATCH 17/17] Retain orig get_secdesc call

Added get_secdesc_with_ctx which is called from get_secdesc
(passing talloc_tos()) Old callsites for get_secdesc  now remain
and only the newer ones call the alternative methods (where a context other
that talloc_tos() is used). This makes the diff from orig code cleaner.
(if this is ok I intend to squash with the main smbcalcs patch)
---
 source3/utils/smbcacls.c | 35 +++++++++++++++++++++--------------
 1 file changed, 21 insertions(+), 14 deletions(-)

diff --git a/source3/utils/smbcacls.c b/source3/utils/smbcacls.c
index e9eccec..0a687c8 100644
--- a/source3/utils/smbcacls.c
+++ b/source3/utils/smbcacls.c
@@ -274,9 +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(TALLOC_CTX *ctx,
-					       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;
@@ -322,6 +322,11 @@ static struct security_descriptor *get_secdesc(TALLOC_CTX *ctx,
         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
 *******************************************************/
@@ -395,7 +400,7 @@ static int cacl_dump(struct cli_state *cli, const char *filename, bool numeric)
 		return EXIT_OK;
 	}
 
-	sd = get_secdesc(talloc_tos(), cli, filename);
+	sd = get_secdesc(cli, filename);
 	if (sd == NULL) {
 		return EXIT_FAILED;
 	}
@@ -429,7 +434,7 @@ static int owner_set(struct cli_state *cli, enum chown_mode change_mode,
 	if (!StringToSid(cli, &sid, new_username))
 		return EXIT_PARSE_ERROR;
 
-	old = get_secdesc(talloc_tos(), cli, filename);
+	old = get_secdesc(cli, filename);
 
 	if (!old) {
 		return EXIT_FAILED;
@@ -515,8 +520,8 @@ set the ACLs on a file given a security descriptor
 *******************************************************/
 
 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, enum acl_mode mode,
+			    bool numeric)
 {
 	struct security_descriptor *old;
 	uint32_t i, j;
@@ -531,7 +536,7 @@ static int cacl_set_from_sd(struct cli_state *cli, const char *filename,
 		 * Do not fetch old ACL when it will be overwritten
 		 * completely with a new one.
 		 */
-		old = get_secdesc(talloc_tos(), cli, filename);
+		old = get_secdesc(cli, filename);
 
 		if (!old) {
 			return EXIT_FAILED;
@@ -643,7 +648,7 @@ static int inherit(struct cli_state *cli, const char *filename,
 	size_t sd_size;
 	int result = EXIT_OK;
 
-	old = get_secdesc(talloc_tos(), cli, filename);
+	old = get_secdesc(cli, filename);
 
 	if (!old) {
 		return EXIT_FAILED;
@@ -663,11 +668,11 @@ static int inherit(struct cli_state *cli, const char *filename,
 
 			/* look at parent and copy in all its inheritable ACL's. */
 			string_replace(temp, '\\', '/');
-			if (!parent_dirname(talloc_tos(),temp,&parentname,NULL)) 			{
+			if (!parent_dirname(talloc_tos(),temp,&parentname,NULL)) {
 				return EXIT_FAILED;
 			}
 			string_replace(parentname, '/', '\\');
-			parent = get_secdesc(talloc_tos(), cli,parentname);
+			parent = get_secdesc(cli,parentname);
 			if (parent == NULL) {
 				return EXIT_FAILED;
 			}
@@ -910,7 +915,7 @@ static NTSTATUS propagate_inherited_aces(char *caclfile,
 		status = NT_STATUS_NO_MEMORY;
 		goto out;
 	}
-	old = get_secdesc(aclctx, cbstate->cli, caclfile);
+	old = get_secdesc_with_ctx(aclctx, cbstate->cli, caclfile);
 
 	if (!old) {
 		status = NT_STATUS_UNSUCCESSFUL;
@@ -1025,7 +1030,8 @@ static NTSTATUS prepare_inheritance_propagation(TALLOC_CTX *ctx, char *filename,
 	struct cli_state *cli = cbstate->cli;
 	enum acl_mode mode = cbstate->mode;
 	struct security_descriptor *sd = NULL;
-	struct security_descriptor *old = get_secdesc(ctx, cli, filename);
+	struct security_descriptor *old = get_secdesc_with_ctx(ctx, cli,
+							      filename);
 	uint32_t j;
 	bool propagate = false;
 
@@ -1101,7 +1107,8 @@ static NTSTATUS get_parents_inheritable_aces(TALLOC_CTX *ctx, char *filename,
 {
 	NTSTATUS result = NT_STATUS_OK;
 	struct cli_state *cli = cbstate->cli;
-	struct security_descriptor *sd = get_secdesc(ctx, cli, filename);
+	struct security_descriptor *sd = get_secdesc_with_ctx(ctx, cli,
+							      filename);
 	struct security_acl *acl_to_add = NULL;
 	int j;
 	if (!sd) {
-- 
2.10.2



More information about the samba-technical mailing list