smbcacls support for automatic inheritance propagation

Noel Power nopower at suse.com
Tue Feb 7 18:43:31 UTC 2017


On 04/02/17 13:49, Ralph Böhme wrote:
> Hi Noel,
>
> O
[...]
> Fmpov please cleanup first and squash first, I'd prefer to look at what you
> believe to be the most sensible patchset then looking at a WIP state. :)
Alrighty :-) please see attached and same can be viewed at
https://cgit.freedesktop.org/~noelp/noelp-samba/log/?h=smbcacls_review%236

thanks alot

Noel
-------------- next part --------------
From 4ba0ae78cc66165c1c641e1c4de7cecac4b9b709 Mon Sep 17 00:00:00 2001
From: David Disseldorp <ddiss at samba.org>
Date: Thu, 14 Nov 2013 16:10:26 +0000
Subject: [PATCH 1/4] add smbcacls test based on test_smbclient_tarmode.pl

Pair-Programmed-With: Noel Power <noel.power at suse.com>
Signed-off-by: Noel Power <noel.power at suse.com>
---
 selftest/selftesthelpers.py           |    1 +
 source3/script/tests/test_smbcacls.pl | 3227 +++++++++++++++++++++++++++++++++
 source3/selftest/tests.py             |   22 +
 3 files changed, 3250 insertions(+)
 create mode 100755 source3/script/tests/test_smbcacls.pl

diff --git a/selftest/selftesthelpers.py b/selftest/selftesthelpers.py
index b0ece36..f83236a 100644
--- a/selftest/selftesthelpers.py
+++ b/selftest/selftesthelpers.py
@@ -184,6 +184,7 @@ def smbtorture4_testsuites(prefix):
 
 
 smbclient3 = binpath('smbclient')
+smbcacls = binpath('smbcacls')
 smbtorture3 = binpath('smbtorture3')
 ntlm_auth3 = binpath('ntlm_auth')
 net = binpath('net')
diff --git a/source3/script/tests/test_smbcacls.pl b/source3/script/tests/test_smbcacls.pl
new file mode 100755
index 0000000..41bbc0b
--- /dev/null
+++ b/source3/script/tests/test_smbcacls.pl
@@ -0,0 +1,3227 @@
+#!/usr/bin/perl
+
+# Unix SMB/CIFS implementation.
+# Test suite for the smbcacls binary
+# Copyright (C) Aurélien Aptel 2013
+# Copyright (C) David Disseldorp 2013
+# Copyright (C) Noel Power 2013
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# TODO - split all common test_smbcacls.pl/test_smbclient_tarmode.pl code into
+# a separate file.
+
+=head1 NAME
+
+C<test_smbcacls.pl> - Test for smbcacls
+
+=cut
+
+use v5.10;
+use strict;
+use warnings;
+
+use Archive::Tar;
+use Data::Dumper;
+use Digest::MD5 qw/md5_hex/;
+use File::Path qw/make_path remove_tree/;
+use File::Spec;
+use File::Temp;
+use Getopt::Long;
+use Pod::Usage;
+use Term::ANSIColor;
+
+sub d {print Dumper @_;}
+
+# DEFAULTS
+# 'our' to make them available in the File package
+our $USER      = '';
+our $PW        = '';
+our $HOST      = '';
+our $SHARE     = '';
+our $DIR       = 'tar_test_dir';
+our $LOCALPATH = '';
+our $TMP       = File::Temp->newdir();
+our $CLI_BIN   = 'smbclient';
+our $CACLS_BIN = 'smbcacls';
+our $SUBUNIT   = 0;
+
+my $SELECTED_TEST = '';
+my $LIST_TEST = 0;
+
+my @SMBARGS   = ();
+
+our $DEBUG = 1;
+our $VERBOSE = 1;
+my $MAN   = 0;
+my $HELP  = 0;
+my $CLEAN = 0;
+
+# all tests
+my @TESTS = (
+    ['simple, single file set ACL', \&test_simple_single_set],
+    ['simple, single file modify ACL', \&test_simple_single_mod],
+    ['simple, single file delete ACL', \&test_simple_single_del],
+    ['simple, single file add ACL', \&test_simple_single_add],
+    ['simple, single object inherit add', \&test_simple_oi_add],
+    ['simple, single object inherit delete', \&test_simple_oi_delete],
+    ['simple, single object inherit modify', \&test_simple_oi_modify],
+    ['simple, single container inherit add', \&test_simple_ci_add],
+    ['simple, single container inherit delete', \&test_simple_ci_delete],
+    ['simple, single container inherit modify', \&test_simple_ci_modify],
+    ['simple, single container/object inherit add', \&test_simple_cioi_add],
+    ['simple, single container/object inherit delete', \&test_simple_cioi_delete],
+    ['simple, single container/object inherit modify', \&test_simple_cioi_modify],
+    ['simple, single inherit set(fail test)', \&test_simple_set_fail],
+    ['simple, single container/object inherit set', \&test_simple_oici_set],
+    ['simple, single container inherit set', \&test_simple_ci_set],
+    ['simple, single container/inherit/np add', \&test_simple_cioinp_add],
+    ['simple, single object inherit/np add', \&test_simple_oinp_add],
+    ['simple, single container inherit/np add', \&test_simple_cinp_add],
+    ['simple, single container/inherit/np delete', \&test_simple_cioinp_delete],
+    ['simple, single object inherit/np delete', \&test_simple_oinp_delete],
+    ['simple, single container inherit/np delete', \&test_simple_cinp_delete],
+    ['simple, single container/object inherit inhibit', \&test_simple_cioi_inhibit],
+);
+
+=head1 SYNOPSIS
+
+ test_smbcacls.pl [options] -- [smbclient options]
+
+ Options:
+    -h, --help    brief help message
+    --man         full documentation
+
+  Environment:
+    -u, --user      USER
+    -p, --password  PW
+    -n, --name      HOST	(required)
+    -s, --share     SHARE	(required)
+    -d, --dir       PATH
+        sub-path to use on the share
+
+    -l, --local-path  PATH	(required)
+        path to the root of the samba share on the machine.
+
+    --cli_bin       BIN
+        path to the smbclient binary to use
+
+    --cacls_bin     BIN
+        path to the smbcacls binary to use
+
+  Test:
+    --list
+       list tests
+
+    --test N
+    --test A-B
+    --test A,B,D-F
+       only run certain tests (accept list and intervals of numbers)
+
+    -v, --verbose
+       be more verbose
+
+    --debug
+       print command and their output (also set -v)
+
+    --subunit
+       print output in subunit format
+
+    --clean
+       clean out DIR path between tests
+
+=cut
+
+GetOptions('u|user=s'       => \$USER,
+           'p|password=s'   => \$PW,
+           'n|name=s'       => \$HOST,
+           's|share=s'      => \$SHARE,
+           'd|dir=s'        => \$DIR,
+           'l|local-path=s' => \$LOCALPATH,
+           'cli_bin=s'    => \$CLI_BIN,
+           'cacls_bin=s'  => \$CACLS_BIN,
+
+           'test=s'         => \$SELECTED_TEST,
+           'list'           => \$LIST_TEST,
+
+           'clean'          => \$CLEAN,
+           'subunit'        => \$SUBUNIT,
+           'debug'          => \$DEBUG,
+           'v|verbose'      => \$VERBOSE,
+           'h|help'         => \$HELP,
+           'man'            => \$MAN) or pod2usage(2);
+
+pod2usage(0) if $HELP;
+pod2usage(-exitval => 0, -verbose => 2) if $MAN;
+list_test(), exit 0 if $LIST_TEST;
+pod2usage(1) unless $HOST;
+pod2usage(1) unless $SHARE;
+pod2usage(1) unless $LOCALPATH;
+pod2usage(1) unless $USER;
+pod2usage(1) unless $PW;
+
+push @SMBARGS, '-U'.$USER.'%'.$PW;
+
+# remaining arguments are passed to smbclient
+push @SMBARGS, @ARGV;
+
+#####
+
+# SANITIZATION
+
+# remove all final slashes from input paths
+$LOCALPATH =~ s{[/\\]+$}{}g;
+$SHARE =~ s{[/\\]+$}{}g;
+$HOST =~ s{[/\\]+$}{}g;
+$DIR =~ s{[/\\]+$}{}g;
+# remove ./ prefix from DIR
+$DIR =~ s{^[\./\\]+}{}g;
+
+if (!-d $LOCALPATH) {
+    die "Local path '$LOCALPATH' is not a directory.\n";
+}
+
+if ($CLEAN) {
+    # clean the whole root first
+    remove_tree($LOCALPATH, { keep_root => 1 });
+}
+
+if ($DEBUG) {
+    $VERBOSE = 1;
+}
+
+#####
+
+# RUN TESTS
+
+my @selection = parse_test_string($SELECTED_TEST);
+
+if ($SELECTED_TEST eq '') {
+    run_test(@TESTS);
+} elsif (@selection > 0) {
+    run_test(@selection);
+} else {
+    die "Test selection '$SELECTED_TEST' is invalid\n";
+}
+
+#################################
+
+=head1 DOCUMENTATION
+
+=head2 Defining a test
+
+=over
+
+=item * Create a function C<test_yourtest>
+
+=item * Use the File module, documented below
+
+=item * Use C<smb_tar>, C<smb_client>, C<smb_cacls>, C<check_tar> or C<check_remote>
+
+=item * Return number of errors
+
+=item * Add function to C<@TESTS>
+
+=back
+
+The function must be placed in the C<@TESTS> list along with a short
+description and optional arguments.
+
+=cut
+
+
+# test smbcacls '--set' attempts to overwrite the ACL for the file
+#
+# before:
+#
+#  +-tar_test_dir/
+#    +-file.1            (I)(F)
+#
+# after/expected:
+#
+#  +-tar_test_dir/
+#    +-file.1            (F)
+#
+
+sub test_simple_single_set
+{
+	my @files;
+	my $acl_str = "ACL:$USER:ALLOWED/0x0/FULL";
+	my $ace;
+	my $ret;
+	my $out;
+
+	my $f = File->new_local("$TMP/file-1");
+	$f->put_remote("file-1");
+
+	($out, $ret) = smb_cacls('--set', $acl_str, $f->smb_remotepath);
+	if ($ret != 0 ) {
+		return 1;
+	}
+	# only a single ACE string in the ACL
+	$ace = ace_parse_str($acl_str);
+	$ret = file_ace_check($f->smb_remotepath, $ace);
+
+	$f->del_remote(1);
+
+	return 0;
+}
+
+
+# test smbcacls '--modify' attempts to modify the ACL for the file
+# (note: first part of the test 'set' ACL to (F) then attempts to modify
+# before:
+#
+#  +-tar_test_dir/
+#    +-file.1            (F)
+#
+# after/expected:
+#
+#  +-tar_test_dir/
+#    +-file.1            (READ)
+#
+sub test_simple_single_mod
+{
+	my @files;
+	my $acl_str = "ACL:$USER:ALLOWED/0x0/FULL";
+	my $ace;
+	my $ret;
+	my $out;
+
+	my $f = File->new_local("$TMP/file-1");
+	$f->put_remote("file-1");
+
+	($out, $ret) = smb_cacls('--set', $acl_str, $f->smb_remotepath);
+	if ($ret != 0 ) {
+		return 1;
+	}
+	# only a single ACE string in the ACL
+	$ace = ace_parse_str($acl_str);
+	$ret = file_ace_check($f->smb_remotepath, $ace);
+	if ($ret != 0) {
+		return 1;
+	}
+
+	# overwrite existing entry
+	$acl_str = "ACL:$USER:ALLOWED/0x0/READ";
+	($out, $ret) = smb_cacls('--modify', $acl_str, $f->smb_remotepath);
+	if ($ret != 0) {
+		return 1;
+	}
+	$ace = ace_parse_str($acl_str);
+	$ret = file_ace_check($f->smb_remotepath, $ace);
+
+	$f->del_remote(1);
+
+	return 0;
+}
+
+# test smbcacls '--delete' attempts to delete the ACL for the file
+# (note: first part of the test 'set' ACL to (F) then attempts to delete
+# before:
+#
+#  +-tar_test_dir/
+#    +-file.1            (F)
+#
+# after/expected:
+#
+#  +-tar_test_dir/
+#    +-file.1            (none) - meaning no (F) ACL for this user
+#
+sub test_simple_single_del
+{
+	my @files;
+	my $acl_str = "ACL:$USER:ALLOWED/0x0/FULL";
+	my $ace;
+	my $ret;
+	my $out;
+	my $f = File->new_local("$TMP/file-1");
+	$f->put_remote("file-1");
+
+	smb_cacls('--set', $acl_str, $f->smb_remotepath);
+
+	# only a single ACE string in the ACL
+	$ace = ace_parse_str($acl_str);
+	$ret = file_ace_check($f->smb_remotepath, $ace);
+	if ($ret != 0) {
+		return 1;
+	}
+
+	($out, $ret) = smb_cacls('--delete', $acl_str, $f->smb_remotepath);
+	if ($ret != 0 ) {
+		return 1;
+	}
+	$ace = ace_parse_str($acl_str);
+	$ret = file_ace_check($f->smb_remotepath, $ace);
+	if ($ret == 0) {
+		say "got ace where not expecting";
+		return 1;
+	}
+
+	$f->del_remote(1);
+
+	return 0;
+}
+
+# test smbcacls '--add' attempts to add the ACL for the file
+# (note: first part of the test 'set' ACL to (F) then attempts to add new ACL
+# before:
+#
+#  +-tar_test_dir/
+#    +-file.1            (F)
+#
+# after/expected:
+#
+#  +-tar_test_dir/       
+#    +-file.1            (F,READ)
+#
+sub test_simple_single_add
+{
+	my @files;
+	my $acl_str = "ACL:$USER:ALLOWED/0x0/FULL";
+	my $dny_acl_str = "ACL:$USER:DENIED/0x0/READ";
+	my $ace;
+	my $ret;
+	my $out;
+
+	my $f = File->new_local("$TMP/file-1");
+	$f->put_remote("file-1");
+
+	($out, $ret) = smb_cacls('--set', $acl_str, $f->smb_remotepath);
+	if ($ret != 0 ) {
+		return 1;
+	}
+	# only a single ACE string in the ACL
+	$ace = ace_parse_str($acl_str);
+	$ret = file_ace_check($f->smb_remotepath, $ace);
+	if ($ret != 0) {
+		say "missing ace";
+		return 1;
+	}
+
+	($out, $ret) = smb_cacls('--add', $dny_acl_str, $f->smb_remotepath);
+	if ($ret != 0 ) {
+		return 1;
+	}
+	$ace = ace_parse_str($dny_acl_str);
+	$ret = file_ace_check($f->smb_remotepath, $ace);
+	if ($ret != 0) {
+		say "missing ace";
+		return 1;
+	}
+
+	$f->del_remote(1);
+
+	return 0;
+}
+
+
+# test smbcacls '--propagate-inheritance --add' which attempts to add the ACL
+# for the file and additionally use inheritance rules to propagate appropriate
+# changes to children
+#
+# This test adds an ACL with (OI)(READ)
+#
+# before:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(I)(F)
+#    | +-file.1            (I)(F)
+#    | +-nested/      (OI)(CI)(I)(F)
+#    |   +-file.2          (I)(F)
+#
+# after/expected:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(I)(F), (OI)(READ)
+#    | +-file.1            (I)(F), (I)(READ)
+#    | +-nested/      (OI)(CI)(I)(F), (OI)(IO)(I)(READ)
+#    |   +-file.2          (I)(F), (I)(READ)
+#
+
+sub test_simple_oi_add
+{
+	my @files;
+	my $dir_add_acl_str = "ACL:$USER:ALLOWED/OI/READ";
+	my $obj_inherited_ace_str = "ACL:$USER:ALLOWED/I/READ";
+	my $dir_inherited_ace_str = "ACL:$USER:ALLOWED/OI|IO|I/READ";
+	my $dir_ace;
+	my $child_file_ace;
+	my $child_dir_ace;
+	my $ret;
+	my $out;
+
+	my $f1 = File->new_local("$TMP/file-1");
+	$f1->put_remote("oi_dir/file-1");
+	my $f2 = File->new_local("$TMP/file-2");
+	$f2->put_remote("oi_dir/nested/file-2");
+
+	($out, $ret) = smb_cacls('--propagate-inheritance', '--add',
+				 $dir_add_acl_str, $f1->smb_remotedir);
+	if ($ret != 0 ) {
+		return 1;
+	}
+
+	# check top level container 'oi_dir' has OI/READ
+	$dir_ace = ace_parse_str($dir_add_acl_str);
+	$ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+	if ($ret != 0) {
+		say "missing ace";
+		return 1;
+	}
+	# file 'oi_dir/file-1' should  have inherited I/READ
+	$child_file_ace = ace_parse_str($obj_inherited_ace_str);
+	$ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+
+	if ($ret != 0) {
+		say "missing ace";
+		return 1;
+	}
+
+	# nested dir  'oi_dir/nested/' should have OI|IO/READ
+	$child_dir_ace = ace_parse_str($dir_inherited_ace_str);
+	$ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+	if ($ret != 0) {
+		say "missing ace";
+		return 1;
+	}
+
+	# nested file 'oi_dir/nested/file-2' should  have inherited I/READ
+	$ret = file_ace_check($f2->smb_remotepath, $child_file_ace);
+	if ($ret != 0) {
+		say "got ace where not expecting";
+		return 1;
+	}
+
+	$f1->del_remote();
+	$f2->del_remote(1);
+
+	return 0;
+}
+# test smbcacls '--propagate-inheritance --add' which attempts to add the ACL
+# for the file and additionally use inheritance rules to propagate appropriate
+# changes to children
+#
+# This test adds an ACL with (OI)(READ)
+#
+# before:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(I)(F), (OI)(IO)(READ)
+#    | +-file.1            (I)(F), (I)(READ)
+#    | +-nested/      (OI)(CI)(I)(F), (OI)(IO)(I)(READ)
+#    |   +-file.2          (I)(F), (I)(READ)
+#
+# after/expected:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(I)(F)
+#    | +-file.1            (I)(F)
+#    | +-nested/      (OI)(CI)(I)(F)
+#    |   +-file.2          (I)(F)
+#
+sub test_simple_oi_delete
+{
+	my @files;
+	my $dir_acl_str = "ACL:$USER:ALLOWED/OI/READ";
+	my $obj_inherited_ace_str = "ACL:$USER:ALLOWED/I/READ";
+	my $dir_inherited_ace_str = "ACL:$USER:ALLOWED/OI|IO|I/READ";
+	my $dir_ace;
+	my $child_file_ace;
+	my $child_dir_ace;
+	my $ret;
+	my $out;
+
+	my $f1 = File->new_local("$TMP/file-1");
+	$f1->put_remote("oi_dir/file-1");
+	my $f2 = File->new_local("$TMP/file-2");
+	$f2->put_remote("oi_dir/nested/file-2");
+
+	# add flags on oi_dir
+	($out, $ret) = smb_cacls('--add', $dir_acl_str, $f1->smb_remotedir);
+	if ($ret != 0 ) {
+		return 1;
+	}
+	# add flags on oi_dir/nested
+	($out, $ret) = smb_cacls('--add', $dir_inherited_ace_str,
+				 $f2->smb_remotedir);
+	if ($ret != 0 ) {
+		return 1;
+	}
+	# add flags on oi_dir/file-1
+	($out, $ret) = 	smb_cacls('--add', $obj_inherited_ace_str,
+		$f1->smb_remotepath);
+	if ($ret != 0 ) {
+		return 1;
+	}
+	# add flags on oi_dir/nested/file-2
+	($out, $ret) = smb_cacls('--add', $obj_inherited_ace_str,
+				 $f2->smb_remotepath);
+	if ($ret != 0 ) {
+		return 1;
+	}
+	($out, $ret) = smb_cacls('--propagate-inheritance', '--delete',
+				 $dir_acl_str,
+		  $f1->smb_remotedir);
+	if ($ret != 0 ) {
+		return 1;
+	}
+
+	# check top level container 'oi_dir' no longer has OI/READ
+	$dir_ace = ace_parse_str($dir_acl_str);
+	$ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+	if ($ret == 0) {
+		say "unexpected ace";
+		return 1;
+	}
+	# file 'oi_dir/file-1' should  no longer have inherited I/READ
+	$child_file_ace = ace_parse_str($obj_inherited_ace_str);
+	$ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+
+	if ($ret == 0) {
+		say "unexpected ace";
+		return 1;
+	}
+
+	# nested dir  'oi_dir/nested/' should no longer have OI|IO/READ
+	$child_dir_ace = ace_parse_str($dir_inherited_ace_str);
+	$ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+	if ($ret == 0) {
+		say "unexpected ace";
+		return 1;
+	}
+
+	# nested file 'oi_dir/nested/file-2' should no longer have inherited I/READ
+	$ret = file_ace_check($f2->smb_remotepath, $child_file_ace);
+	if ($ret == 0) {
+		say "got ace where not expecting";
+		return 1;
+	}
+	$f1->del_remote();
+	$f2->del_remote(1);
+
+	return 0;
+}
+
+# test smbcacls '--propagate-inheritance --add' which attempts to add the ACL
+# for the file and additionally use inheritance rules to propagate appropriate
+# changes to children
+#
+# This test adds an ACL with (CI)(READ)
+#
+# before:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(I)(F)
+#    | +-file.1            (I)(F)
+#    | +-nested/      (OI)(CI)(I)(F)
+#    |   +-file.2          (I)(F)
+#
+# after/expected:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(I)(F), (CI)(READ)
+#    | +-file.1            (I)(F)
+#    | +-nested/      (OI)(CI)(I)(F), (CI)((I)(READ)
+#    |   +-file.2          (I)(F)
+sub test_simple_ci_add
+{
+	my @files;
+	my $dir_add_acl_str = "ACL:$USER:ALLOWED/CI/READ";
+	my $file_inherited_ace_str = "ACL:$USER:ALLOWED/I/READ";
+	my $dir_inherited_ace_str = "ACL:$USER:ALLOWED/CI|I/READ";
+	my $dir_ace;
+	my $child_file_ace;
+	my $child_dir_ace;
+	my $ret;
+	my $out;
+
+	my $f1 = File->new_local("$TMP/file-1");
+	$f1->put_remote("oi_dir/file-1");
+	my $f2 = File->new_local("$TMP/file-2");
+	$f2->put_remote("oi_dir/nested/file-2");
+	($out, $ret) = smb_cacls('--propagate-inheritance', '--add',
+				 $dir_add_acl_str, $f1->smb_remotedir);
+	if ($ret != 0 ) {
+		return 1;
+	}
+	# check top level container 'oi_dir' has CI/READ
+	$dir_ace = ace_parse_str($dir_add_acl_str);
+	$ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+	if ($ret != 0) {
+		say "missing ace";
+		return 1;
+	}
+
+
+	$child_file_ace = ace_parse_str($file_inherited_ace_str);
+	# nested file 'oi_dir/file-1' should NOT have inherited I/READ
+	$ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+	if ($ret == 0) {
+		say "got ace where not expecting";
+		return 1;
+	}
+
+	# nested dir  'oi_dir/nested/' should have CI|I|READ
+	$child_dir_ace = ace_parse_str($dir_inherited_ace_str);
+	$ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+	if ($ret != 0) {
+		return 1;
+	}
+
+	$f1->del_remote();
+	$f2->del_remote(1);
+
+	return 0;
+}
+
+# test smbcacls '--propagate-inheritance --add' which attempts to add the ACL
+# for the file and additionally use inheritance rules to propagate appropriate
+# changes to children
+#
+# This test delete an ACL with (CI)(READ)
+#
+# before:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(I)(F), (CI)(READ)
+#    | +-file.1            (I)(F)
+#    | +-nested/      (OI)(CI)(I)(F), (CI)((I)(READ)
+#    |   +-file.2          (I)(F)
+#
+# after/expected:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(I)(F)
+#    | +-file.1            (I)(F)
+#    | +-nested/      (OI)(CI)(I)(F)
+#    |   +-file.2          (I)(F)
+sub test_simple_ci_delete
+{
+	my @files;
+	my $dir_acl_str = "ACL:$USER:ALLOWED/CI/READ";
+	my $file_inherited_ace_str = "ACL:$USER:ALLOWED/I/READ";
+	my $dir_inherited_ace_str = "ACL:$USER:ALLOWED/CI|I/READ";
+	my $dir_ace;
+	my $child_file_ace;
+	my $child_dir_ace;
+	my $ret;
+	my $out;
+
+	my $f1 = File->new_local("$TMP/file-1");
+	$f1->put_remote("oi_dir/file-1");
+	my $f2 = File->new_local("$TMP/file-2");
+	$f2->put_remote("oi_dir/nested/file-2");
+
+	# add flags on oi_dir
+	($out, $ret) = smb_cacls('--add', $dir_acl_str, $f1->smb_remotedir);
+	if ($ret != 0 ) {
+		return 1;
+	}
+	# add flags on oi_dir/nested
+	($out, $ret) = smb_cacls('--add', $dir_inherited_ace_str,
+				 $f2->smb_remotedir);
+	if ($ret != 0 ) {
+		return 1;
+	}
+	# make sure no (I|RX) flags on oi_dir/file-1
+	($out, $ret) = smb_cacls('--delete', $file_inherited_ace_str,
+				 $f1->smb_remotepath);
+	if ($ret != 0 ) {
+		return 1;
+	}
+	# make sure no (I|RX) flags on oi_dir/nested/file-2
+	($out, $ret) = smb_cacls('--delete', $file_inherited_ace_str,
+				 $f2->smb_remotepath);
+	if ($ret != 0 ) {
+		return 1;
+	}
+	($out, $ret) = smb_cacls('--propagate-inheritance', '--delete',
+				 $dir_acl_str, $f1->smb_remotedir);
+
+	if ($ret != 0 ) {
+		return 1;
+	}
+	# check top level container 'oi_dir' no longer has CI/READ
+	$dir_ace = ace_parse_str($dir_acl_str);
+	$ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+
+	if ($ret == 0) {
+		say "unexpectex ace";
+		return 1;
+	}
+
+	$child_file_ace = ace_parse_str($file_inherited_ace_str);
+	# nested file 'oi_dir/file-1' should NOT have inherited I/READ
+	$ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+	if ($ret == 0) {
+		say "got ace where not expecting";
+		return 1;
+	}
+
+	# nested dir  'oi_dir/nested/' should no longer have CI|I|READ
+	$child_dir_ace = ace_parse_str($dir_inherited_ace_str);
+	$ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+	if ($ret == 0) {
+		say "unexpected ace";
+		return 1;
+	}
+
+	$f1->del_remote();
+	$f2->del_remote(1);
+
+	return 0;
+}
+
+# test smbcacls '--propagate-inheritance --add' which attempts to add the ACL
+# for the file and additionally use inheritance rules to propagate appropriate
+# changes to children
+#
+# This test adds an ACL with (CI)(OI)(READ)
+#
+# before:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(I)(F)
+#    | +-file.1            (I)(F)
+#    | +-nested/      (OI)(CI)(I)(F)
+#    |   +-file.2          (I)(F)
+#
+# after/expected:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(I)(F), (CI)(OI)READ)
+#    | +-file.1            (I)(F), (I)(READ)
+#    | +-nested/      (OI)(CI)(I)(F), (CI)(OI)(I)(READ)
+#    |   +-file.2          (I)(F), (I)(READ)
+sub test_simple_cioi_add
+{
+	my @files;
+	my $dir_add_acl_str = "ACL:$USER:ALLOWED/OI|CI/READ";
+	my $file_inherited_ace_str = "ACL:$USER:ALLOWED/I/READ";
+	my $dir_inherited_ace_str = "ACL:$USER:ALLOWED/OI|CI|I/READ";
+	my $dir_ace;
+	my $child_file_ace;
+	my $child_dir_ace;
+	my $ret;
+	my $out;
+
+	my $f1 = File->new_local("$TMP/file-1");
+	$f1->put_remote("oi_dir/file-1");
+	my $f2 = File->new_local("$TMP/file-2");
+	$f2->put_remote("oi_dir/nested/file-2");
+
+	($out, $ret) = smb_cacls('--propagate-inheritance', '--add',
+				 $dir_add_acl_str, $f1->smb_remotedir);
+	if ($ret != 0 ) {
+		return 1;
+	}
+	# check top level container 'oi_dir' has OI|CI/READ
+	$dir_ace = ace_parse_str($dir_add_acl_str);
+	$ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+	if ($ret != 0) {
+		say "missing expected ace";
+		return 1;
+	}
+
+
+	$child_file_ace = ace_parse_str($file_inherited_ace_str);
+	# nested file 'oi_dir/file-1' should have inherited I/READ
+	$ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+	if ($ret != 0) {
+		say "missing expected ace";
+		return 1;
+	}
+
+	# nested dir  'oi_dir/nested/' should have OI|CI|I|READ
+	$child_dir_ace = ace_parse_str($dir_inherited_ace_str);
+	$ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+	if ($ret != 0) {
+		say "missing expected ace";
+		return 1;
+	}
+
+	$f1->del_remote();
+	$f2->del_remote(1);
+
+	return 0;
+}
+
+# test smbcacls '--propagate-inheritance --delete' which attempts to delete the
+# ACL for the file and additionally use inheritance rules to propagate
+# appropriate changes to children
+#
+# This test deletes an ACL with (CI)(OI)(READ)
+#
+# before:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(I)(F), (CI)(OI)(READ)
+#    | +-file.1            (I)(F), (I)(READ)
+#    | +-nested/      (OI)(CI)(I)(F), (CI)(OI)(I)(READ)
+#    |   +-file.2          (I)(F), (I)(READ)
+#
+# after/expected:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(I)(F)
+#    | +-file.1            (I)(F)
+#    | +-nested/      (OI)(CI)(I)(F)
+#    |   +-file.2          (I)(F)
+#
+
+sub test_simple_cioi_delete
+{
+	my @files;
+	my $dir_acl_str = "ACL:$USER:ALLOWED/OI|CI/READ";
+	my $file_inherited_ace_str = "ACL:$USER:ALLOWED/I/READ";
+	my $dir_inherited_ace_str = "ACL:$USER:ALLOWED/OI|CI|I/READ";
+	my $dir_ace;
+	my $child_file_ace;
+	my $child_dir_ace;
+	my $ret;
+	my $out;
+
+	my $f1 = File->new_local("$TMP/file-1");
+	$f1->put_remote("oi_dir/file-1");
+	my $f2 = File->new_local("$TMP/file-2");
+	$f2->put_remote("oi_dir/nested/file-2");
+
+	# add flags on oi_dir
+	($out, $ret) = smb_cacls('--add', $dir_acl_str, $f1->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
+	# add flags on oi_dir/nested
+	($out, $ret) = smb_cacls('--add', $dir_inherited_ace_str,
+				 $f2->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
+	# add flags on oi_dir/file-1
+	($out, $ret) = smb_cacls('--add', $file_inherited_ace_str,
+				 $f1->smb_remotepath);
+	if ($ret != 0) {
+		return 1;
+	}
+	# add flags on oi_dir/nested/file-2
+	($out, $ret) = smb_cacls('--add', $file_inherited_ace_str,
+				 $f2->smb_remotepath);
+	if ($ret != 0) {
+		return 1;
+	}
+
+	($out, $ret) = smb_cacls('--propagate-inheritance', '--delete',
+				 $dir_acl_str,
+		  $f1->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
+
+	# check top level container 'oi_dir' no longer has OI|CI/READ
+	$dir_ace = ace_parse_str($dir_acl_str);
+	$ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+
+	if ($ret == 0) {
+		say "unexpected ace";
+		return 1;
+	}
+
+	$child_file_ace = ace_parse_str($file_inherited_ace_str);
+	# nested file 'oi_dir/file-1' should NOT have inherited I/READ
+	$ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+	if ($ret == 0) {
+		say "unexpected ace";
+		return 1;
+	}
+
+	# nested dir  'oi_dir/nested/' should no longer have OI|CI|I|READ
+	$child_dir_ace = ace_parse_str($dir_inherited_ace_str);
+	$ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+	if ($ret == 0) {
+		return 1;
+	}
+
+	$f1->del_remote();
+	$f2->del_remote(1);
+
+	return 0;
+}
+
+# test smbcacls '--propagate-inheritance --modify' which attempts to modify the
+# ACLfor the file and additionally use inheritance rules to propagate
+# appropriate changes to children
+#
+# This test first adds an ACL with (CI)(OI)(R), then it modifies that acl to be
+# (CI)(OI)(D) - where D == 0x00110000
+#
+# before:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (CI)(OI)(R)
+#    | +-file.1       (I)(R)
+#    | +-nested/      (CI)(OI)(I)(R)
+#    |   +-file.2     (I)(R)
+#
+# after/expected:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (CI)(OI)(CHANGE)
+#    | +-file.1       (I)(CHANGE)
+#    | +-nested/      (CI)(OI)(I)(CHANGE)
+#    |   +-file.2     (I)(CHANGE)
+
+sub test_simple_cioi_modify
+{
+	my @files;
+	my $dir_acl_str = "ACL:$USER:ALLOWED/OI|CI/R";
+	my $file_inherited_ace_str = "ACL:$USER:ALLOWED/I/R";
+	my $dir_inherited_ace_str = "ACL:$USER:ALLOWED/OI|CI|I/R";
+
+	my $dir_mod_acl_str = "ACL:$USER:ALLOWED/OI|CI/CHANGE";
+	my $file_mod_inherited_ace_str = "ACL:$USER:ALLOWED/I/CHANGE";
+	my $dir_mod_inherited_ace_str = "ACL:$USER:ALLOWED/OI|CI|I/CHANGE";
+
+	my $dir_ace;
+	my $child_file_ace;
+	my $child_dir_ace;
+	my $ret;
+	my $out;
+
+	my $f1 = File->new_local("$TMP/file-1");
+	$f1->put_remote("oi_dir/file-1");
+	my $f2 = File->new_local("$TMP/file-2");
+	$f2->put_remote("oi_dir/nested/file-2");
+
+	# add flags on oi_dir
+
+	# This is somwhat artificial, we need to add a new acl to the directory
+	# so that the following modify operation doesn't fail. Previously
+	# '--modify' was used in place of '--add' but that resulted in failure
+	# to access the directory ( or even modify the acl ).
+	# Note: when running this test against a windows server it seems that
+	# running as Administrator ensures best results
+
+	($out, $ret) = smb_cacls('--add', $dir_acl_str, $f1->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
+	# add flags on oi_dir/nested
+	($out, $ret) = smb_cacls('--add', $dir_inherited_ace_str,
+				 $f2->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
+	# add flags on oi_dir/file-1
+	($out, $ret) = smb_cacls('--add', $file_inherited_ace_str,
+				 $f1->smb_remotepath);
+	if ($ret != 0) {
+		return 1;
+	}
+	# add flags on oi_dir/nested/file-2
+	($out, $ret) = smb_cacls('--add', $file_inherited_ace_str,
+				 $f2->smb_remotepath);
+	if ($ret != 0) {
+		return 1;
+	}
+	
+	($out, $ret) = smb_cacls('--propagate-inheritance', '--modify',
+				 $dir_mod_acl_str,
+		  $f1->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
+
+	# check top level container 'oi_dir' has OI|CI/CHANGE
+	$dir_ace = ace_parse_str($dir_mod_acl_str);
+	$ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+	if ($ret != 0) {
+		return 1;
+	}
+
+	$child_file_ace = ace_parse_str($file_mod_inherited_ace_str);
+	# nested file 'oi_dir/file-1' should have inherited I|CHANGE
+	$ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+	if ($ret != 0) {
+		say "missing expected ace";
+		return 1;
+	}
+
+	# nested dir  'oi_dir/nested/' should have OI|CI|I|CHANGE
+	$child_dir_ace = ace_parse_str($dir_mod_inherited_ace_str);
+	$ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+	if ($ret != 0) {
+		return 1;
+	}
+
+	$f1->del_remote();
+	$f2->del_remote(1);
+
+	return 0;
+}
+# test smbcacls '--propagate-inheritance --modify' which attempts to modify ACL
+# for the file and additionally use inheritance rules to propagate appropriate
+# changes to children
+#
+# This test first adds an ACL with (OI)(R), then it modifies that acl to be
+# (OI)(D) - where D == 0x00110000
+#
+# before:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(IO)(R)
+#    | +-file.1       (I)(R)
+#    | +-nested/      (OI)(IO)(I)(R)
+#    |   +-file.2     (I)(R)
+#
+# after/expected:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(IO)(CHANGE)
+#    | +-file.1       (I)(CHANGED)
+#    | +-nested/      (OI)(IO)(I)(CHANGED)
+#    |   +-file.2     (I)(CHANGED)
+sub test_simple_oi_modify
+{
+	my @files;
+	my $explict_access_ace_str = "ACL:$USER:ALLOWED/0x0/RWD";
+
+
+	my $dir_mod_acl_str = "ACL:$USER:ALLOWED/OI/CHANGE";
+	my $file_mod_inherited_ace_str = "ACL:$USER:ALLOWED/I/CHANGE";
+	my $dir_mod_inherited_ace_str = "ACL:$USER:ALLOWED/OI|IO|I/CHANGE";
+
+	my $dir_ace;
+	my $child_file_ace;
+	my $child_dir_ace;
+	my $ret;
+	my $out;
+
+	my $f1 = File->new_local("$TMP/file-1");
+	$f1->put_remote("oi_dir/file-1");
+	my $f2 = File->new_local("$TMP/file-2");
+	$f2->put_remote("oi_dir/nested/file-2");
+
+	# add flags on oi_dir
+
+	# This is somwhat artificial, we need to add a new acl to the directory
+	# so that the following modify operation doesn't fail. Previously
+	# '--modify' was used in place of '--add' but that resulted in failure
+	# to access the directory ( or even modify the acl ).
+	# Note: when running this test against a windows server it seems that
+	# running as Administrator ensures best results
+
+	($out, $ret) = smb_cacls('--add', $explict_access_ace_str,
+				 $f1->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
+	# add flags on oi_dir/nested
+	($out, $ret) = smb_cacls('--add', $explict_access_ace_str,
+				 $f2->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
+	# add flags on oi_dir/file-1
+	($out, $ret) = smb_cacls('--add', $explict_access_ace_str,
+				 $f1->smb_remotepath);
+	if ($ret != 0) {
+		return 1;
+	}
+	# add flags on oi_dir/nested/file-2
+	($out, $ret) = smb_cacls('--add', $explict_access_ace_str,
+				 $f2->smb_remotepath);
+	if ($ret != 0) {
+		return 1;
+	}
+	($out, $ret) = smb_cacls('--propagate-inheritance', '--modify',
+				 $dir_mod_acl_str, $f1->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
+
+	# check top level container 'oi_dir' has OI/CHANGE
+	$dir_ace = ace_parse_str($dir_mod_acl_str);
+	$ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+	if ($ret != 0) {
+		return 1;
+	}
+	# file 'oi_dir/file-1' should  have inherited I/CHANGE
+	$child_file_ace = ace_parse_str($file_mod_inherited_ace_str);
+	$ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+
+	if ($ret != 0) {
+		return 1;
+	}
+
+	# nested dir  'oi_dir/nested/' should have OI|IO/CHANGE
+	$child_dir_ace = ace_parse_str($dir_mod_inherited_ace_str);
+	$ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+	if ($ret != 0) {
+		return 1;
+	}
+
+	# nested file 'oi_dir/nested/file-2' should  have inherited I/CHANGE
+	$ret = file_ace_check($f2->smb_remotepath, $child_file_ace);
+	if ($ret != 0) {
+		say "got ace where not expecting";
+		return 1;
+	}
+
+	$f1->del_remote();
+	$f2->del_remote(1);
+
+	return 0;
+}
+# test smbcacls '--propagate-inheritance --modify' which attempts to modify ACL
+# for the file and additionally use inheritance rules to propagate appropriate
+# changes to children
+#
+# This test first adds an ACL with (CI)(R), then it modifies that acl to be
+# (CI)(D) - where D == 0x00110000
+#
+# before:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (CI)(R)
+#    | +-file.1            (I)(F)
+#    | +-nested/      (CI)(I)(R)
+#    |   +-file.2          (I)(F)
+#
+# after/expected:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (CI)(CHANGE)
+#    | +-file.1            (I)(F)
+#    | +-nested/      (CI)(I)(CHANGE)
+#    |   +-file.2          (I)(F)
+
+sub test_simple_ci_modify
+{
+	my @files;
+	my $dir_acl_str = "ACL:$USER:ALLOWED/CI/READ";
+	my $file_inherited_ace_str = "ACL:$USER:ALLOWED/I/READ";
+	my $dir_inherited_ace_str = "ACL:$USER:ALLOWED/CI|I/READ";
+
+	my $dir_mod_acl_str = "ACL:$USER:ALLOWED/CI/CHANGE";
+	my $file_mod_inherited_ace_str = "ACL:$USER:ALLOWED/I/CHANGE";
+	my $dir_mod_inherited_ace_str = "ACL:$USER:ALLOWED/CI|I/CHANGE";
+	my $delete_ace_str = "ACL:$USER:ALLOWED/0x0/RWD";
+
+	my $dir_ace;
+	my $child_file_ace;
+	my $child_dir_ace;
+	my $ret;
+	my $out;
+
+	my $f1 = File->new_local("$TMP/file-1");
+	$f1->put_remote("oi_dir/file-1");
+	my $f2 = File->new_local("$TMP/file-2");
+	$f2->put_remote("oi_dir/nested/file-2");
+
+	# add flags on oi_dir
+
+	# This is somwhat artificial, we need to add a new acl to the directory
+	# so that the following modify operation doesn't fail. Previously
+	# '--modify' was used in place of '--add' but that resulted in failure
+	# to access the directory ( or even modify the acl ).
+	# Note: when running this test against a windows server it seems that
+	# running as Administrator ensures best results
+
+	($out, $ret) = smb_cacls('--add', $dir_acl_str, $f1->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
+	# add flags on oi_dir/nested
+	($out, $ret) = smb_cacls('--add', $dir_inherited_ace_str,
+				 $f2->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
+
+	($out, $ret) = smb_cacls('--propagate-inheritance', '--modify',
+				 $dir_mod_acl_str, $f1->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
+
+	# check top level container 'oi_dir' has CI/CHANGE
+	$dir_ace = ace_parse_str($dir_mod_acl_str);
+	$ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+	if ($ret != 0) {
+		return 1;
+	}
+
+
+	$child_file_ace = ace_parse_str($file_mod_inherited_ace_str);
+	# nested file 'oi_dir/file-1' should NOT have inherited I/CHANGE
+	$ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+	if ($ret == 0) {
+		say "got ace where not expecting";
+		return 1;
+	}
+
+	# nested dir  'oi_dir/nested/' should have OI|I/CHANGE
+	$child_dir_ace = ace_parse_str($dir_mod_inherited_ace_str);
+	$ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+	if ($ret != 0) {
+		return 1;
+	}
+
+	# set some flags to allow us to delete the files
+	smb_cacls('--set', $delete_ace_str, $f1->smb_remotepath);
+	smb_cacls('--set', $delete_ace_str, $f2->smb_remotepath);
+	$f1->del_remote();
+	$f2->del_remote(1);
+
+	return 0;
+}
+
+# test smbcacls '--propagate-inheritance --set' which attempts to set the ACL
+# for the file and additionally use inheritance rules to propagate appropriate
+# changes to children
+#
+# This test adds an ACL with (CI)(OI)(READ)
+#
+# before:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(I)(F)
+#    | +-file.1            (I)(F)
+#    | +-nested/      (OI)(CI)(I)(F)
+#    |   +-file.2          (I)(F)
+#
+# after/expected:
+# fail, oid_dir has inheritance enabled, set should fail and exit with '1'
+
+sub test_simple_set_fail()
+{
+	my @files;
+	my $dir_acl_str = "ACL:$USER:ALLOWED/OI|CI/R";
+	my $file_inherited_ace_str = "ACL:$USER:ALLOWED/I/R";
+	my $dir_inherited_ace_str = "ACL:$USER:ALLOWED/OI|CI|I/R";
+	my $dir_ace;
+	my $child_file_ace;
+	my $child_dir_ace;
+	my $ret;
+	my $out;
+
+	my $f1 = File->new_local("$TMP/file-1");
+	$f1->put_remote("oi_dir/file-1");
+	my $f2 = File->new_local("$TMP/file-2");
+	$f2->put_remote("oi_dir/nested/file-2");
+
+	($out, $ret) = smb_cacls('--propagate-inheritance', '--set',
+				 $dir_acl_str, $f1->smb_remotedir);
+
+	if ($ret == 0 ) {
+		say "smb_cacls '--set' succeeded unexpectedly while processing container with inheritance enabled";	
+		return 1;
+	}
+	$f1->del_remote();
+	$f2->del_remote(1);
+	return 0;
+}
+
+# test smbcacls '--propagate-inheritance --set' which attempts to set the ACL
+# for the file and additionally use inheritance rules to propagate appropriate
+# changes to children
+#
+# This test adds an ACL with (CI)(OI)(RWD) additionally it removes 
+# inheritance from oi_dir
+#
+# before:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(I)(F)
+#    | +-file.1            (I)(F)
+#    | +-nested/      (OI)(CI)(I)(F)
+#    |   +-file.2          (I)(F)
+#
+# after/expected:
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(RWD)
+#    | +-file.1            (I)(RWD)
+#    | +-nested/      (OI)(CI)(I)(RWD)
+#    |   +-file.2          (I)(RWD)
+#
+
+sub test_simple_oici_set()
+{
+	my @files;
+	my $dir_acl_str = "ACL:$USER:ALLOWED/OI|CI/RWD";
+	my $file_inherited_ace_str = "ACL:$USER:ALLOWED/I/RWD";
+	my $dir_inherited_ace_str = "ACL:$USER:ALLOWED/OI|CI|I/RWD";
+	my $dir_ace;
+	my $child_file_ace;
+	my $child_dir_ace;
+	my $ret;
+	my $out;
+
+	my $f1 = File->new_local("$TMP/file-1");
+	$f1->put_remote("oi_dir/file-1");
+	my $f2 = File->new_local("$TMP/file-2");
+	$f2->put_remote("oi_dir/nested/file-2");
+
+	($out, $ret) = smb_cacls('--inherit=copy', $f1->smb_remotedir);
+
+	# smb_cacls --inherit=remove 
+	if ($ret != 0 ) {
+		say "failed to remove inheritance enabled";	
+		return 1;
+	}
+
+	($out, $ret) = smb_cacls('--propagate-inheritance', '--set',
+				 $dir_acl_str, $f1->smb_remotedir);
+	if ($ret != 0 ) {
+		return 1;
+	}
+	# check top level container 'oi_dir' has OI|CI/RWD
+	$dir_ace = ace_parse_str($dir_acl_str);	
+	$ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+	if ($ret != 0) {
+		return 1;
+	}
+	# check nested file has I/RW
+	$child_file_ace = ace_parse_str($file_inherited_ace_str);	
+	$ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+	if ($ret != 0) {
+		return 1;
+	}
+
+	# check nested dir has OI|CI|I/RW
+	$child_dir_ace = ace_parse_str($dir_inherited_ace_str);	
+	$ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+	if ($ret != 0) {
+		return 1;
+	}
+	$f1->del_remote();
+	$f2->del_remote(1);
+	return 0;
+}
+
+# test smbcacls '--propagate-inheritance --set' which attempts to set the ACL
+# for the file and additionally use inheritance rules to propagate appropriate
+# changes to children
+#
+# This test adds an ACL with (CI)(RWD) additionally it removes 
+# inheritance from oi_dir
+#
+# before:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(I)(F)
+#    | +-file.1            (I)(F)
+#    | +-nested/      (OI)(CI)(I)(F)
+#    |   +-file.2          (I)(F)
+#
+# after/expected:
+#  +-tar_test_dir/    (01)(CI)(I)(RWD)
+#    +-oi_dir/        (CI)(RWD)
+#    | +-file.1       
+#    | +-nested/      (CI)(I)(RWD)
+#    |   +-file.2     
+#
+
+sub test_simple_ci_set()
+{
+	my @files;
+	my $dir_acl_str = "ACL:$USER:ALLOWED/CI/RWD";
+	my $file_inherited_ace_str = "ACL:$USER:ALLOWED/I/RWD";
+	my $dir_inherited_ace_str = "ACL:$USER:ALLOWED/CI|I/RWD";
+	my $delete_ace_str = "ACL:$USER:ALLOWED/0x0/RWD";
+	my $dir_ace;
+	my $child_file_ace;
+	my $child_dir_ace;
+	my $ret;
+	my $out;
+
+	my $f1 = File->new_local("$TMP/file-1");
+	$f1->put_remote("oi_dir/file-1");
+	my $f2 = File->new_local("$TMP/file-2");
+	$f2->put_remote("oi_dir/nested/file-2");
+
+	($out, $ret) = smb_cacls('--inherit=copy', $f1->smb_remotedir);
+
+	# smb_cacls --inherit=remove 
+	if ($ret != 0 ) {
+		say "failed to remove inheritance enabled";	
+		return 1;
+	}
+	($out, $ret) = smb_cacls('--propagate-inheritance', '--set',
+				 $dir_acl_str, $f1->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
+
+	my $nacl;
+	$nacl = num_acls_for_file($f1->smb_remotedir);
+
+	# Although there maybe a couple of users with associated acl(s)
+	# before set, after set there should only be 1 acl
+	if ($nacl != 1) {
+		say "unexpected number of acls ($nacl) after set";
+		return 1;
+	}
+
+	# check top level container 'oi_dir' has OI|CI/RWD
+	$dir_ace = ace_parse_str($dir_acl_str);	
+	$ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+	if ($ret != 0) {
+		return 1;
+	}
+
+	# note can't check file because it has no ACL ( due to CI )
+
+	# check nested dir has OI|CI|I/RWD
+	$child_dir_ace = ace_parse_str($dir_inherited_ace_str);	
+	$ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+	if ($ret != 0) {
+		return 1;
+	}
+	# set some flags to allow us to delete the files
+	smb_cacls('--set', $delete_ace_str, $f1->smb_remotepath);
+	smb_cacls('--set', $delete_ace_str, $f2->smb_remotepath);
+	$f1->del_remote();
+	$f2->del_remote(1);
+	return 0;
+}
+
+# test smbcacls '--propagate-inheritance --add' which attempts to add the ACL
+# for the file and additionally use inheritance rules to propagate appropriate
+# changes to children
+#
+# This test adds an ACL with (CI)(OI)(NP)(CHANGE)
+# (NP) - no propagation should not propagate the changes any further containers
+#
+# before:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(I)(F)
+#    | +-file.1            (I)(F)
+#    | +-nested/      (OI)(CI)(I)(F)
+#    |   +-file.2          (I)(F)
+#
+# after/expected:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(I)(F), (CI)(OI)(NP)(CHANGE)
+#    | +-file.1            (I)(F), (I)(CHANGE)
+#    | +-nested/      (OI)(CI)(I)(F), (I)(CHANGE)
+#    |   +-file.2          (I)(F)
+sub test_simple_cioinp_add
+{
+	my @files;
+	my $dir_add_acl_str = "ACL:$USER:ALLOWED/OI|CI|NP/CHANGE";
+	my $inherited_ace_str = "ACL:$USER:ALLOWED/I/CHANGE";
+	my $dir_ace;
+	my $child_file_ace;
+	my $child_dir_ace;
+	my $ret;
+	my $out;
+
+	my $f1 = File->new_local("$TMP/file-1");
+	$f1->put_remote("oi_dir/file-1");
+	my $f2 = File->new_local("$TMP/file-2");
+	$f2->put_remote("oi_dir/nested/file-2");
+
+	($out, $ret) = smb_cacls('--propagate-inheritance', '--add',
+				 $dir_add_acl_str, $f1->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
+
+	# check top level container 'oi_dir' has OI|CI|NP/READ
+	$dir_ace = ace_parse_str($dir_add_acl_str);
+	$ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+	if ($ret != 0) {
+		return 1;
+	}
+
+
+	$child_file_ace = ace_parse_str($inherited_ace_str);
+	# nested file 'oi_dir/file-1' should have inherited I/CHANGE
+	$ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+	if ($ret != 0) {
+		say "missing expected ace";
+		return 1;
+	}
+
+	# nested dir  'oi_dir/nested/file-2' should NOT have I/CHANGE
+	$child_dir_ace = ace_parse_str($inherited_ace_str);
+	$ret = file_ace_check($f2->smb_remotepath, $child_dir_ace);
+	if ($ret == 0) {
+		say "unexpected ace";
+		return 1;
+	}
+
+	$child_file_ace = ace_parse_str($inherited_ace_str);
+	# nested dir 'oi_dir/nested' should have inherited I/CHANGE
+	$ret = file_ace_check($f2->smb_remotedir, $child_file_ace);
+	if ($ret != 0) {
+		say "missing expected ace";
+		return 1;
+	}
+
+	$f1->del_remote();
+	$f2->del_remote(1);
+
+	return 0;
+}
+
+# test smbcacls '--propagate-inheritance --add' which attempts to add the ACL
+# for the file and additionally use inheritance rules to propagate appropriate
+# changes to children
+#
+# This test adds an ACL with (OI)(NP)(CHANGE)
+# (NP) - no propagation should not propagate the changes any further containers
+#
+# before:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(I)(F)
+#    | +-file.1            (I)(F)
+#    | +-nested/      (OI)(CI)(I)(F)
+#    |   +-file.2          (I)(F)
+#
+# after/expected:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(I)(F), (OI)(NP)(CHANGE)
+#    | +-file.1            (I)(F), (I)(CHANGE)
+#    | +-nested/      (OI)(CI)(I)(F)
+#    |   +-file.2          (I)(F)
+sub test_simple_oinp_add
+{
+	my @files;
+	my $dir_add_acl_str = "ACL:$USER:ALLOWED/OI|NP/CHANGE";
+	my $inherited_ace_str = "ACL:$USER:ALLOWED/I/CHANGE";
+	my $dir_ace;
+	my $child_file_ace;
+	my $child_dir_ace;
+	my $ret;
+	my $out;
+
+	my $f1 = File->new_local("$TMP/file-1");
+	$f1->put_remote("oi_dir/file-1");
+	my $f2 = File->new_local("$TMP/file-2");
+	$f2->put_remote("oi_dir/nested/file-2");
+
+	($out, $ret) = smb_cacls('--propagate-inheritance', '--add',
+				 $dir_add_acl_str, $f1->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
+
+	# check top level container 'oi_dir' has OI|NP/READ
+	$dir_ace = ace_parse_str($dir_add_acl_str);
+	$ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+	if ($ret != 0) {
+		return 1;
+	}
+
+
+	$child_file_ace = ace_parse_str($inherited_ace_str);
+	# nested file 'oi_dir/file-1' should have inherited I/CHANGE
+	$ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+	if ($ret != 0) {
+		say "missing expected ace";
+		return 1;
+	}
+
+	# nested dir  'oi_dir/nested' should NOT have I/CHANGE
+	$child_dir_ace = ace_parse_str($inherited_ace_str);
+	$ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+	if ($ret != 0) {
+		say "unexpected ace";
+		return 1;
+	}
+
+	$child_file_ace = ace_parse_str($inherited_ace_str);
+	# nested file 'oi_dir/nested/file-1' should NOT have inherited I/CHANGE
+	$ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+	if ($ret != 0) {
+		say "unexpected ace";
+		return 1;
+	}
+
+	$f1->del_remote();
+	$f2->del_remote(1);
+
+	return 0;
+}
+
+# test smbcacls '--propagate-inheritance --add' which attempts to add the ACL
+# for the file and additionally use inheritance rules to propagate appropriate
+# changes to children
+#
+# This test adds an ACL with (CI)(NP)(CHANGE)
+# (NP) - no propagation should not propagate the changes any further containers
+#
+# before:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(I)(F)
+#    | +-file.1            (I)(F)
+#    | +-nested/      (OI)(CI)(I)(F)
+#    |   +-file.2          (I)(F)
+#
+# after/expected:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(I)(F), (CI)(NP)(CHANGE)
+#    | +-file.1            (I)(F)
+#    | +-nested/      (OI)(CI)(I)(F), (I)(CHANGE)
+#    |   +-file.2          (I)(F)
+sub test_simple_cinp_add
+{
+	my @files;
+	my $dir_add_acl_str = "ACL:$USER:ALLOWED/CI|NP/CHANGE";
+	my $inherited_ace_str = "ACL:$USER:ALLOWED/I/CHANGE";
+	my $dir_ace;
+	my $child_file_ace;
+	my $child_dir_ace;
+	my $ret;
+	my $out;
+
+	my $f1 = File->new_local("$TMP/file-1");
+	$f1->put_remote("oi_dir/file-1");
+	my $f2 = File->new_local("$TMP/file-2");
+	$f2->put_remote("oi_dir/nested/file-2");
+
+	($out, $ret) = smb_cacls('--propagate-inheritance', '--add',
+				 $dir_add_acl_str, $f1->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
+
+	# check top level container 'oi_dir' has CI|NP/READ
+	$dir_ace = ace_parse_str($dir_add_acl_str);
+	$ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+	if ($ret != 0) {
+		return 1;
+	}
+
+
+	$child_file_ace = ace_parse_str($inherited_ace_str);
+	# nested file 'oi_dir/file-1' should NOT have inherited I/CHANGE
+	$ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+	if ($ret == 0) {
+		say "unexpected ace";
+		return 1;
+	}
+
+	# nested dir  'oi_dir/nested' should have I/CHANGE
+	$child_dir_ace = ace_parse_str($inherited_ace_str);
+	$ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+	if ($ret != 0) {
+		say "missing expected ace";
+		return 1;
+	}
+
+	$child_file_ace = ace_parse_str($inherited_ace_str);
+	# nested file 'oi_dir/nested/file-1' should NOT have inherited I/CHANGE
+	$ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+	if ($ret == 0) {
+		say "unexpected ace";
+		return 1;
+	}
+
+	$f1->del_remote();
+	$f2->del_remote(1);
+	return 0;
+}
+
+# test smbcacls '--propagate-inheritance --delete' which attempts to delete
+# the ACL for the file and additionally use inheritance rules to propagate
+# appropriate changes to children
+#
+# This test adds an ACL with (CI)(OI)(NP)(CHANGE)
+# (NP) - no propagation should not propagate the changes any further containers
+#
+# before:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(I)(F), (CI)(OI)(NP)(CHANGE)
+#    | +-file.1            (I)(F), (I)(CHANGE)
+#    | +-nested/      (OI)(CI)(I)(F), (I)(CHANGE)
+#    |   +-file.2          (I)(F)
+#
+# after/expected:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(I)(F)
+#    | +-file.1            (I)(F)
+#    | +-nested/      (OI)(CI)(I)(F)
+#    |   +-file.2          (I)(F)
+#
+sub test_simple_cioinp_delete
+{
+	my @files;
+	my $dir_add_acl_str = "ACL:$USER:ALLOWED/OI|CI|NP/CHANGE";
+	my $inherited_ace_str = "ACL:$USER:ALLOWED/I/CHANGE";
+	my $dir_ace;
+	my $child_file_ace;
+	my $child_dir_ace;
+	my $ret;
+	my $out;
+
+	my $f1 = File->new_local("$TMP/file-1");
+	$f1->put_remote("oi_dir/file-1");
+	my $f2 = File->new_local("$TMP/file-2");
+	$f2->put_remote("oi_dir/nested/file-2");
+
+	# set up 'before' permissions
+	($out, $ret) = smb_cacls('--add', $dir_add_acl_str, $f1->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
+	($out, $ret) = smb_cacls('--add', $inherited_ace_str,
+				 $f1->smb_remotepath);
+	if ($ret != 0) {
+		return 1;
+	}
+	($out, $ret) = smb_cacls('--add', $inherited_ace_str,
+				 $f2->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
+
+	($out, $ret) = smb_cacls('--propagate-inheritance', '--delete',
+				 $dir_add_acl_str,
+		  $f1->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
+
+	# check top level container 'oi_dir' does NOT have OI|CI|NP/READ
+	$dir_ace = ace_parse_str($dir_add_acl_str);
+	$ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+	if ($ret == 0) {
+		say "unexpected ace";
+		return 1;
+	}
+
+
+	$child_file_ace = ace_parse_str($inherited_ace_str);
+	# nested file 'oi_dir/file-1' should NOT have inherited I/CHANGE
+	$ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+	if ($ret == 0) {
+		say "unexpected ace";
+		return 1;
+	}
+
+	$child_file_ace = ace_parse_str($inherited_ace_str);
+	# nested dir 'oi_dir/nested' should NOT have inherited I/CHANGE
+	$ret = file_ace_check($f2->smb_remotedir, $child_file_ace);
+	if ($ret == 0) {
+		say "unexpected ace";
+		return 1;
+	}
+
+	$f1->del_remote();
+	$f2->del_remote(1);
+
+	return 0;
+}
+
+# test smbcacls '--propagate-inheritance --delete' which attempts to delete the
+# ACL for the file and additionally use inheritance rules to propagate
+# appropriate changes to children
+#
+# This test adds an ACL with (CI)(NP)(CHANGE)
+# (NP) - no propagation should not propagate the changes any further containers
+#
+# before:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(I)(F), (CI)(NP)(CHANGE)
+#    | +-file.1            (I)(F)
+#    | +-nested/      (OI)(CI)(I)(F), (I)(CHANGE)
+#    |   +-file.2          (I)(F)
+#
+# after/expected:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(I)(F)
+#    | +-file.1            (I)(F)
+#    | +-nested/      (OI)(CI)(I)(F)
+#    |   +-file.2          (I)(F)
+
+sub test_simple_cinp_delete
+{
+	my @files;
+	my $dir_add_acl_str = "ACL:$USER:ALLOWED/CI|NP/CHANGE";
+	my $inherited_ace_str = "ACL:$USER:ALLOWED/I/CHANGE";
+	my $dir_ace;
+	my $child_file_ace;
+	my $child_dir_ace;
+	my $ret;
+	my $out;
+
+	my $f1 = File->new_local("$TMP/file-1");
+	$f1->put_remote("oi_dir/file-1");
+	my $f2 = File->new_local("$TMP/file-2");
+	$f2->put_remote("oi_dir/nested/file-2");
+
+	# set up ace(s) to match 'before'
+	($out, $ret) = smb_cacls('--add', $dir_add_acl_str, $f1->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
+	($out, $ret) = smb_cacls('--add', $inherited_ace_str,
+				 $f2->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
+
+	($out, $ret) = smb_cacls('--propagate-inheritance', '--delete',
+				 $dir_add_acl_str, $f1->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
+
+	# check top level container 'oi_dir' doesn't have CI|NP/READ
+	$dir_ace = ace_parse_str($dir_add_acl_str);
+	$ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+	if ($ret == 0) {
+		say "unexpected ace";
+		return 1;
+	}
+
+
+	$child_file_ace = ace_parse_str($inherited_ace_str);
+	# nested file 'oi_dir/file-1' should NOT have inherited I/CHANGE
+	$ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+	if ($ret == 0) {
+		say "unexpected ace";
+		return 1;
+	}
+
+	# nested dir  'oi_dir/nested' should NOT have I/CHANGE
+	$child_dir_ace = ace_parse_str($inherited_ace_str);
+	$ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+	if ($ret == 0) {
+		say "unexpected ace";
+		return 1;
+	}
+
+	$child_file_ace = ace_parse_str($inherited_ace_str);
+	# nested file 'oi_dir/nested/file-1' should NOT have inherited I/CHANGE
+	$ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+	if ($ret == 0) {
+		say "unexpected ace";
+		return 1;
+	}
+
+	$f1->del_remote();
+	$f2->del_remote(1);
+
+	return 0;
+}
+
+# test smbcacls '--propagate-inheritance --delete' which attempts to delete the
+# ACL for the file and additionally use inheritance rules to propagate
+# appropriate changes to children
+#
+# This test adds an ACL with (OI)(NP)(CHANGE)
+# (NP) - no propagation should not propagate the changes any further containers
+#
+# before:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(I)(F), (OI)(NP)(CHANGE)
+#    | +-file.1            (I)(F), (I)(CHANGE)
+#    | +-nested/      (OI)(CI)(I)(F)
+#    |   +-file.2          (I)(F)
+#
+# after/expected:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(I)(F)
+#    | +-file.1            (I)(F)
+#    | +-nested/      (OI)(CI)(I)(F)
+#    |   +-file.2          (I)(F)
+
+sub test_simple_oinp_delete
+{
+	my @files;
+	my $dir_add_acl_str = "ACL:$USER:ALLOWED/OI|NP/CHANGE";
+	my $inherited_ace_str = "ACL:$USER:ALLOWED/I/CHANGE";
+	my $dir_ace;
+	my $child_file_ace;
+	my $child_dir_ace;
+	my $ret;
+	my $out;
+
+	my $f1 = File->new_local("$TMP/file-1");
+	$f1->put_remote("oi_dir/file-1");
+	my $f2 = File->new_local("$TMP/file-2");
+	$f2->put_remote("oi_dir/nested/file-2");
+
+	# set up 'before' permissions
+	($out, $ret) = smb_cacls('--add', $dir_add_acl_str, $f1->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
+	($out, $ret) = smb_cacls('--add', $inherited_ace_str,
+				 $f1->smb_remotepath);
+	if ($ret != 0) {
+		return 1;
+	}
+
+	($out, $ret) = smb_cacls('--propagate-inheritance', '--delete',
+				 $dir_add_acl_str, $f1->smb_remotedir);
+	if ($ret != 0) {
+		return 1;
+	}
+
+	# check top level container 'oi_dir' does NOT have OI|NP/READ
+	$dir_ace = ace_parse_str($dir_add_acl_str);
+	$ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+	if ($ret == 0) {
+		say "unexpected ace";
+		return 1;
+	}
+
+
+	$child_file_ace = ace_parse_str($inherited_ace_str);
+	# nested file 'oi_dir/file-1' should NOT have inherited I/CHANGE
+	$ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+	if ($ret == 0) {
+		say "unexpected ace";
+		return 1;
+	}
+
+	$f1->del_remote();
+	$f2->del_remote(1);
+
+	return 0;
+}
+
+# test smbcacls '--propagate-inheritance --add' which attempts to add the ACL
+# for the file and additionally use inheritance rules to propagate appropriate
+# changes to children. In particular it tests that inheritance removed does
+# indeed prevent inheritance propagation
+#
+# This test adds an ACL with (CI)(OI)(CHANGE) at oi_dir
+#
+# Note: Inheritance has been removed ( and ace(s) copied ) at
+#   tar_test_dir/oi_dir/nested
+#
+# before:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(I)(F)
+#    | +-file.1            (I)(F)
+#    | +-nested/      (OI)(CI)(F)
+#    |   +-file.2          (I)(F)
+#
+# after/expected:
+#
+#  +-tar_test_dir/    (01)(CI)(I)(F)
+#    +-oi_dir/        (OI)(CI)(I)(F), (CI)(OI)(CHANGE)
+#    | +-file.1            (I)(F), (I)((CHANGE)
+#    | +-nested/      (OI)(CI)(F)
+#    |   +-file.2          (I)(F)
+sub test_simple_cioi_inhibit
+{
+	my @files;
+	my $dir_add_acl_str = "ACL:$USER:ALLOWED/OI|CI/CHANGE";
+	my $file_inherited_ace_str = "ACL:$USER:ALLOWED/I/CHANGE";
+	my $dir_inherited_ace_str = "ACL:$USER:ALLOWED/OI|CI|I/CHANGE";
+	my $dir_ace;
+	my $child_file_ace;
+	my $child_dir_ace;
+	my $ret;
+	my $out;
+
+	my $f1 = File->new_local("$TMP/file-1");
+	$f1->put_remote("oi_dir/file-1");
+	my $f2 = File->new_local("$TMP/file-2");
+	$f2->put_remote("oi_dir/nested/file-2");
+
+	($out, $ret) = smb_cacls('--inherit=copy', $f2->smb_remotedir);
+
+	# smb_cacls --inherit=remove 
+	if ($ret != 0 ) {
+		say "failed to remove inheritance enabled";	
+		return 1;
+	}
+
+	smb_cacls('--propagate-inheritance', '--add', $dir_add_acl_str,
+		  $f1->smb_remotedir);
+
+	# check top level container 'oi_dir' has OI|CI/CHANGE
+	$dir_ace = ace_parse_str($dir_add_acl_str);
+	$ret = file_ace_check($f1->smb_remotedir, $dir_ace);
+	if ($ret != 0) {
+		say "missing expected ace";
+		return 1;
+	}
+
+
+	$child_file_ace = ace_parse_str($file_inherited_ace_str);
+	# nested file 'oi_dir/file-1' should have inherited I/CHANGE
+	$ret = file_ace_check($f1->smb_remotepath, $child_file_ace);
+	if ($ret != 0) {
+		say "missing expected ace";
+		return 1;
+	}
+
+	# nested dir  'oi_dir/nested/' should NOT have OI|CI|I/CHANGE
+	$child_dir_ace = ace_parse_str($dir_inherited_ace_str);
+	$ret = file_ace_check($f2->smb_remotedir, $child_dir_ace);
+	if ($ret == 0) {
+		say "unexpected ace";
+		return 1;
+	}
+
+	# nested file  'oi_dir/nested/file-2' should NOT have I/CHANGE
+	$child_dir_ace = ace_parse_str($dir_inherited_ace_str);
+	$ret = file_ace_check($f2->smb_remotepath, $child_dir_ace);
+	if ($ret == 0) {
+		say "unexpected ace";
+		return 1;
+	}
+	$f1->del_remote();
+	$f2->del_remote(1);
+
+	return 0;
+}
+
+#################################
+
+# IMPLEMENTATION
+
+=head2 Useful functions
+
+Here are a list of useful functions and helpers to define tests.
+
+=cut
+
+# list test number and description
+sub list_test {
+    my $i = 0;
+    for (@TESTS) {
+        my ($desc, $f, @args) = @$_;
+        printf "%2d.\t%s\n", $i++, $desc;
+    }
+}
+
+sub run_test {
+    if ($SUBUNIT) {
+        run_test_subunit(@_);
+    } else {
+        run_test_normal(@_);
+    }
+}
+
+sub run_test_normal {
+    for (@_) {
+        my ($desc, $f, @args) = @$_;
+        my $err;
+
+        reset_env();
+        say "TEST: $desc";
+        if ($VERBOSE) {
+            $err = $f->(@args);
+        } else {
+            # turn off STDOUT
+            open my $saveout, ">&STDOUT";
+            open STDOUT, '>', File::Spec->devnull();
+            $err = $f->(@args);
+            open STDOUT, ">&", $saveout;
+        }
+        print_res($err);
+        print "\n";
+    }
+    reset_env();
+}
+
+sub run_test_subunit {
+    for (@_) {
+        my ($desc, $f, @args) = @$_;
+        my $err;
+        my $str = '';
+
+        reset_env();
+        say "test: $desc";
+
+        # capture output in $buf
+        my $buf = '';
+        open my $handle, '>', \$buf;
+        select $handle;
+
+        # check for die() calls
+        eval {
+            $err = $f->(@args);
+        };
+        if ($@) {
+            $str = $@;
+            $err = 1;
+        }
+        close $handle;
+
+        # restore output
+        select STDOUT;
+
+        # result string is output + eventual exception message
+        $str = $buf.$str;
+
+        printf "%s: %s [\n%s]\n", ($err > 0 ? "failure" : "success"), $desc, $str;
+    }
+    reset_env();
+}
+
+sub parse_test_string {
+    my $s = shift;
+    my @tests = ();
+
+    if (!length($s)) {
+        return ();
+    }
+
+    for (split /,/, $s) {
+        if (/^\d+$/) {
+            if ($_ >= @TESTS) {
+                return ();
+            }
+            push @tests, $TESTS[$_];
+        }
+        elsif (/^(\d+)-(\d+)$/) {
+            my ($min, $max) = sort ($1, $2);
+            if ($max >= @TESTS) {
+                return ();
+            }
+
+            for ($min..$max) {
+                push @tests, $TESTS[$_];
+            }
+        }
+        else {
+            return ();
+        }
+    }
+
+    return @tests;
+}
+
+sub print_res {
+    my $err = shift;
+    if ($err) {
+        printf " RES: %s%d ERR%s\n", color('bold red'), $err, color 'reset';
+    } else {
+        printf " RES: %sOK%s\n", color('bold green'), color 'reset';
+    }
+}
+
+sub make_env {
+    my ($exts, $dirs) = @_;
+    my @all;
+    my $nb = 0;
+    for my $dir (@$dirs) {
+        for (@$exts) {
+            my $fn = $dir . "file$nb." . $_;
+            my $f = File->new_remote($fn, 'ABSPATH');
+            $f->delete_on_destruction(1);
+            push @all, $f;
+            $nb++;
+        }
+    }
+
+    @all;
+}
+
+=head3 C<combine ( \@set, $n )>
+
+=head3 C<combine ( ['a', 'b', 'c'], 2 )>
+
+Return a list of all possible I<n>-uplet (or combination of C<$n> element) of C<@set>.
+
+=cut
+sub combine {
+    my ($list, $n) = @_;
+    die "Insufficient list members" if $n > @$list;
+
+    return map [$_], @$list if $n <= 1;
+
+    my @comb;
+
+    for (my $i = 0; $i+$n <= @$list; $i++) {
+        my $val = $list->[$i];
+        my @rest = @$list[$i+1..$#$list];
+        push @comb, [$val, @$_] for combine(\@rest, $n-1);
+    }
+
+    return @comb;
+}
+
+
+=head3 C<reset_remote( )>
+
+Remove all files in the server C<$DIR> (not root)
+
+=cut
+sub reset_remote {
+    # remove_tree($LOCALPATH . '/'. $DIR);
+    # make_path($LOCALPATH . '/'. $DIR);
+    if ($CLEAN) {
+	    remove_tree($LOCALPATH, {keep_root => 1});
+    }
+    make_path($LOCALPATH, {keep_root => 1});
+}
+
+=head3 C<reset_tmp( )>
+
+Remove all files in the temp directory C<$TMP>
+
+=cut
+sub reset_tmp {
+    remove_tree($TMP, { keep_root => 1 });
+}
+
+
+=head3 C<reset_env( )>
+
+Remove both temp and remote (C<$DIR>) files
+
+=cut
+sub reset_env {
+    reset_tmp();
+    reset_remote();
+}
+
+=head3 C<file_list ( @files )>
+
+Make a multiline string of all the files remote path, one path per line.
+
+C<@files> must be a list of C<File> instance.
+
+=cut
+sub file_list {
+    my @files = @_;
+    my $s = '';
+    for (@files) {
+        $s .= $_->remotepath."\n";
+    }
+    return $s;
+}
+
+# remove leading "./"
+sub remove_dot {
+    my $s = shift;
+    $s =~ s{^\./}{};
+    $s;
+}
+
+=head3 C<check_remote( $remotepath, \@files )>
+
+Check if C<$remotepath> has B<exactly> all the C<@files>.
+
+Print a summary on STDOUT.
+
+C<@files> must be a list of C<File> instance.
+
+=cut
+sub check_remote {
+    my ($subpath, $files) = @_;
+    my (%done, %expected);
+    my (@less, @more, @diff);
+
+    for (@$files) {
+        my $fn = remove_dot($_->remotepath);
+        $expected{$fn} = $_;
+        $done{$fn} = 0;
+    }
+
+    my %remote;
+    File::walk(sub { $remote{remove_dot($_->remotepath)} = $_ }, File::tree($subpath));
+
+    for my $rfile (sort keys %remote) {
+
+        # files that shouldn't be there
+        if (!exists $expected{$rfile}) {
+            say " +    $rfile";
+            push @more, $rfile;
+            next;
+        }
+
+        # same file multiple times
+        if ($done{$rfile} > 0) {
+            $done{$rfile}++;
+            push @more, $rfile;
+            printf " +%3d %s\n", $done{$rfile}, $rfile;
+            next;
+        }
+
+        $done{$rfile}++;
+
+        # different file
+        my $rmd5 = $remote{$rfile}->md5;
+        if ($expected{$rfile}->md5 ne $rmd5) {
+            say " !    $rfile ($rmd5)";
+            push @diff, $rfile;
+            next;
+        }
+
+        say "      $rfile";
+    }
+
+    # file that should have been in tar
+    @less = grep { $done{$_} == 0 } sort keys %done;
+    for (@less) {
+        say " -    $_";
+    }
+
+    # summary
+    printf("\t%d files, +%d, -%d, !%d\n",
+           scalar keys %done,
+           scalar @more,
+           scalar @less,
+           scalar @diff);
+    return (@more + @less + @diff); # nb of errors
+}
+
+sub ace_dump
+{
+	my ($ace) = @_;
+	my $key;
+	my $value;
+
+	while (($key, $value) = each(%$ace)) {
+		print $key . "=" . $value . ", ";
+	}
+	print "\n";
+}
+
+sub ace_cmp
+{
+	my ($ace_left, $ace_right) = @_;
+	my $key;
+	my $value;
+	# need to copy hashes, otherwise entries are lost on failure!
+	my %ace1 = %$ace_left;
+	my %ace2 = %$ace_right;
+
+	while (($key, $value) = each(%ace1)) {
+		# ignore the users domain, only compare username
+		next if ($key eq "user_dom");
+
+		unless (defined $ace2{$key}) {
+			print "no entry for: $key\n";
+			return 1;
+		}
+		if ($value ne $ace2{$key}) {
+			print "mismatch: $key:$value != $key:$ace2{$key}\n";
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+=head3 C<ace_parse_str( $ace_str )>
+
+Parses the smbacls C<$ace_str> in the format of:
+	ACL:SAMBA-TEST\ddiss:ALLOWED/0x0/FULL
+The ACE is returned as hashref with the following members:
+->user_dom:	Domain assigned to ACE user, if any
+->user:		ACE user, without domain portion
+->type:		allow or deny
+->inherit:	inheritace flags
+->permissions:	permissions flags
+
+=cut
+sub ace_parse_str
+{
+	my ($ace_str) = @_;
+	my %ace;
+
+	my @splat = split(":", $ace_str);
+
+	if ($splat[0] ne "ACL") {
+		say "invalid ACE string: $ace_str";
+		return undef;
+	}
+
+	#check for domain component in username
+	if (index($splat[1], "\\") >= 0) {
+		($ace{user_dom}, $ace{user}) = split(/\\/, $splat[1]);
+	} elsif (index($splat[1], "/") >= 0) {
+		($ace{user_dom}, $ace{user}) = split(m%/%, $splat[1]);
+	} else {
+		$ace{user} = $splat[1];
+	}
+
+	($ace{type}, $ace{inherit}, $ace{permissions}) = split(/\//, $splat[2]);
+
+	return \%ace;
+}
+=head3 C<num_acls_for_file( $file )>
+
+count ACL that C<$file> has
+
+C<$file> must be a C<File> instance.
+
+=cut
+sub num_acls_for_file
+{
+	my ($file_remotepath) = @_;
+	my @aces;
+	my @out;
+	my ($output, $ret) =  smb_cacls("--get", $file_remotepath); 
+	@out = split("\n", $output);
+	@aces = grep(/^ACL/, @out);
+	return scalar @aces;
+}
+=head3 C<file_ace_check( $file, $ace )>
+
+Check that C<$file> has an ACE matching C<$ace>.
+
+C<$file> must be a C<File> instance.
+
+=cut
+sub file_ace_check
+{
+	my ($file_remotepath, $ace) = @_;
+	my $missing = 1;
+	my @aces;
+	my @out;
+	my ($output, $ret) =  smb_cacls("--get", $file_remotepath); 
+	@out = split("\n", $output);
+	@aces = grep(/^ACL/, @out);
+	# trim the domain portion of users
+	foreach (@aces) {
+		my $acl_ace = ace_parse_str($_);
+		return 1 unless defined $acl_ace;
+		# only compare aces for the same user and type
+		next unless (($acl_ace->{user} eq $ace->{user})
+			  && ($acl_ace->{type} eq $ace->{type}));
+
+		say "found ACE for $acl_ace->{user}";
+		if (ace_cmp($acl_ace, $ace) != 0) {
+			print "differences between file ACE: ";
+			ace_dump($acl_ace);
+			print "and expected ACE: ";
+			ace_dump($ace);
+		}
+		else {
+			say "matched ACE for $acl_ace->{user}";
+			ace_dump($ace);
+			$missing = 0;
+			last;
+		}
+	}
+
+	return $missing;
+}
+
+=head3 C<smb_client ( @args )>
+
+Run smbclient with C<@args> passed as argument and return output.
+
+Each element of C<@args> becomes one escaped argument of smbclient.
+
+Host, share, user, password and the additionnal arguments provided on
+the command-line are already inserted.
+
+The output contains both the C<STDOUT> and C<STDERR>.
+
+Die if smbclient crashes or exits with an error code.
+
+=cut
+sub smb_client {
+    my (@args) = @_;
+
+    my $fullpath = "//$HOST/$SHARE";
+    my $cmd = sprintf("%s %s %s",
+                      quotemeta($CLI_BIN),
+                      quotemeta($fullpath),
+                      join(' ', map {quotemeta} (@SMBARGS, @args)));
+
+    if ($DEBUG) {
+        my $tmp = $cmd;
+        $tmp =~ s{\\([./+-])}{$1}g;
+        say color('bold yellow'), $tmp, color('reset');
+    }
+
+    my $out = `$cmd 2>&1`;
+    my $err = $?;
+    my $errstr = '';
+    # handle abnormal exit
+    if ($err == -1) {
+        $errstr = "failed to execute $cmd: $!\n";
+    }
+    elsif ($err & 127) {
+        $errstr = sprintf "child died with signal %d (%s)\n", ($err & 127), $cmd;
+    }
+    elsif ($err >> 8) {
+        $errstr = sprintf "child exited with value %d (%s)\n", ($err >> 8), $cmd;
+    }
+
+    if ($DEBUG) {
+        say $out;
+    }
+
+    if ($err) {
+        die "ERROR: $errstr";
+    }
+    return $out;
+}
+
+=head3 C<smb_cacls ( @args )>
+
+Run smbcacls with C<@args> passed as argument and return output.
+
+Each element of C<@args> becomes one escaped argument of smbcacls.
+
+Host, share, user, password and the additionnal arguments provided on
+the command-line are already inserted.
+
+The output contains both the C<STDOUT> and C<STDERR>.
+
+Die if smbcacls crashes or exits with an error code.
+
+=cut
+sub smb_cacls
+{
+	my (@args) = @_;
+
+	# last arg is the filename, share path must be immediately prior
+	my $file = pop(@args);
+	my $sharepath = "//$HOST/$SHARE";
+	my $cmd = sprintf("%s %s %s %s",
+			  quotemeta($CACLS_BIN),
+			  join(' ', map {quotemeta} (@SMBARGS, @args)),
+			  quotemeta($sharepath),
+			  quotemeta($file));
+
+	if ($DEBUG) {
+		my $tmp = $cmd;
+		$tmp =~ s{\\([./+-])}{$1}g;
+		say color('bold yellow'), $tmp, color('reset');
+	}
+
+	my $out = `$cmd 2>&1`;
+	my $err = $?;
+	my $errstr = '';
+	my $ret = 0;
+	# handle abnormal exit
+	if ($err == -1) {
+		$errstr = "failed to execute $cmd: $!\n";
+	} elsif ($err & 127) {
+		$errstr = sprintf "child died with signal %d (%s)\n", ($err & 127), $cmd;
+	} elsif ($err >> 8) {
+		$errstr = sprintf "child exited with value %d (%s)\n", ($err >> 8), $cmd;
+		$ret = ($err >> 8);
+	}
+
+	if ($DEBUG) {
+		say $out;
+	}
+
+	if ($err) {
+		# die for abnormal error (cmd didn't run or some signal caught)
+		die "ERROR: $errstr" if $ret == 0;
+	}
+	return ($out, $ret);
+}
+
+=head3 C<random( $min, $max )>
+
+Return integer in C<[ $min ; $max ]>
+
+=cut
+sub random {
+    my ($min, $max) = @_;
+    ($min, $max) = ($max, $min) if ($min > $max);
+    $min + int(rand($max - $min));
+}
+
+#################################
+
+package File;
+
+=head2 The File module
+
+All the test should use the C<File> class. It has nice functions and
+methods to deal with paths, to create random files, to list the
+content of the server, to change attributes, etc.
+
+There are 2 kinds of C<File>: remote and local.
+
+=over
+
+=item * Remote files are accessible on the server.
+
+=item * Local files are not.
+
+=back
+
+Thus, some methods only works on remote files. If they do not make
+sense for local ones, they always return undef.
+
+=cut
+use File::Basename;
+use File::Path qw/make_path remove_tree/;
+use Digest::MD5 qw/md5_hex/;
+use Scalar::Util 'blessed';
+
+=head3 Constructors
+
+=head4 C<< File->new_remote($path [, $abs, $size]) >>
+
+Creates a file accessible on the server at C<$DIR/$path> ie. not at the
+root of the share and write C<$size> random bytes.
+
+If no size is provided, a random size is chosen.
+
+If you want to remove the automatic prefix C<$DIR>, set C<$abs> to 1.
+
+The file is created without any DOS attributes.
+
+If C<$path> contains non-existent directories, they are automatically
+created.
+
+=cut
+sub new_remote {
+    my ($class, $path, $abs, $size) = @_;
+    my ($file, $dir) = fileparse($path);
+
+    $dir = '' if $dir eq './';
+    my $loc;
+
+    if ($abs) {
+        $loc = cleanpath($main::LOCALPATH.'/'.$dir);
+    } else {
+        $dir = cleanpath($main::DIR.'/'.$dir);
+        $loc = cleanpath($main::LOCALPATH.'/'.$dir);
+    }
+
+    make_path($loc);
+
+    my $self = bless {
+        'attr' => {qw/r 0 s 0 h 0 a 0 d 0 n 0/},
+        'dir'  => $dir,
+        'name' => $file,
+        'md5'  => create_file($loc.'/'.$file, $size),
+        'remote' => 1,
+    }, $class;
+
+    $self->set_attr();
+
+    $self;
+}
+
+=head4 C<< File->new_local($abs_path [, $data]) >>
+
+Creates a file at C<$abs_path> with $data in it on the system.
+If $data is not provided, fill it with random bytes.
+
+=cut
+sub new_local {
+    my ($class, $path, $data) = @_;
+    my ($file, $dir) = fileparse($path);
+
+    make_path($dir);
+
+    my $md5;
+
+    if (defined $data) {
+        open my $f, '>', $path or die "can't write in $path: $!";
+        print $f $data;
+        close $f;
+        $md5 = md5_hex($data);
+    } else {
+        $md5 = create_file($path);
+    }
+
+    my $self = {
+        'attr' => {qw/r 0 s 0 h 0 a 0 d 0 n 0/},
+        'dir'  => $dir,
+        'name' => $file,
+        'md5'  => $md5,
+        'remote' => 0,
+    };
+
+    bless $self, $class;
+}
+
+=head3 Methods
+
+=head4 C<< $f->localpath >>
+
+Return path on the system eg. F</opt/samba/share/test_tar_mode/file>
+
+=cut
+sub localpath {
+    my $s = shift;
+    if ($s->{remote}) {
+        return cleanpath($main::LOCALPATH.'/'.$s->remotepath);
+    }
+    else {
+        return cleanpath($s->{dir}.'/'.$s->{name});
+    }
+}
+
+=head4 C<< $f->remotepath >>
+
+Return path on the server share.
+
+Return C<undef> if the file is local.
+
+=cut
+sub remotepath {
+    my ($s) = @_;
+    return undef if !$s->{remote};
+    my $r = $s->{dir}.'/'.$s->{name};
+    $r =~ s{^/}{};
+    return cleanpath($r);
+}
+
+=head4 C<< $f->smb_remotepath >>
+
+Return SMB path on the server share.
+
+Return C<undef> if the file is local.
+
+=cut
+sub smb_remotepath {
+    my ($s) = @_;
+    return undef if !$s->{remote};
+    my $r = $s->remotepath;
+    $r =~ s{/}{\\}g;
+    return $r;
+}
+
+=head4 C<< $f->remotedir >>
+
+Return the directory path of the file on the server.
+
+Like C<< $f->remotepath >> but without the final file name.
+
+=cut
+sub remotedir {
+    my $s = shift;
+    return undef if !$s->{remote};
+    cleanpath($s->{dir});
+}
+
+=head4 C<< $f->smb_remotepath >>
+
+Return SMB directory on the server share.
+
+Return C<undef> if the file is local.
+
+=cut
+sub smb_remotedir {
+    my ($s) = @_;
+    return undef if !$s->{remote};
+    my $r = $s->remotedir;
+    $r =~ s{/}{\\}g;
+    return $r;
+}
+
+=head4 C<< $f->tarpath >>
+
+Return path as it would appear in a tar archive.
+
+Like C<< $f->remotepath >> but prefixed with F<./>
+
+=cut
+sub tarpath {
+    my $s = shift;
+    return undef if !$s->{remote};
+    my $r = $s->remotepath;
+    $r =~ s{^\./+}{};
+    return $r;
+}
+
+=head4 C<< $f->delete_on_destruction( 0 ) >>
+
+=head4 C<< $f->delete_on_destruction( 1 ) >>
+
+By default, a C<File> is not deleted on the filesystem when it is not
+referenced anymore in Perl memory.
+
+When set to 1, the destructor unlink the file if it is not already removed.
+If the C<File> created directories when constructed, it does not remove them.
+
+=cut
+sub delete_on_destruction {
+    my ($s, $delete) = @_;
+    $s->{delete_on_destruction} = $delete;
+}
+
+=head4 C<< $f->set_attr( ) >>
+
+=head4 C<< $f->set_attr( 'a' ) >>
+
+=head4 C<< $f->set_attr( 'a', 'r', 's', 'h' ) >>
+
+Remove all DOS attributes and only set the one provided.
+
+=cut
+sub set_attr {
+    my ($s, @flags) = @_;
+    return undef if !$s->{remote};
+
+    $s->{attr} = {qw/r 0 s 0 h 0 a 0 d 0 n 0/};
+
+    for (@flags) {
+        $s->{attr}{lc($_)} = 1;
+    }
+
+    my $file = $s->{name};
+    my @args;
+    if ($s->remotedir) {
+        push @args, '-D', $s->remotedir;
+    }
+    main::smb_client(@args, '-c', qq{setmode "$file" -rsha});
+    if (@flags && $flags[0] !~ /n/i) {
+        main::smb_client(@args, '-c', qq{setmode "$file" +}.join('', @flags));
+    }
+}
+
+=head4 C<< $f->attr_any( 'a' ) >>
+
+=head4 C<< $f->attr_any( 'a', 's', ... ) >>
+
+Return 1 if the file has any of the DOS attributes provided.
+
+=cut
+sub attr_any {
+    my ($s, @flags) = @_;
+    for (@flags) {
+        return 1 if $s->{attr}{$_};
+    }
+    0;
+}
+
+
+=head4 C<< $f->attr( 'a' ) >>
+
+=head4 C<< $f->attr( 'a', 's', ... ) >>
+
+Return 1 if the file has all the DOS attributes provided.
+
+=cut
+sub attr {
+    my ($s, @flags) = @_;
+    for (@flags) {
+        return 0 if !$s->{attr}{$_};
+    }
+    1;
+}
+
+=head4 C<< $f->attr_str >>
+
+Return DOS attributes as a compact string.
+
+  Read-only, hiden, system, archive => "rhsa"
+
+=cut
+sub attr_str {
+    my $s = shift;
+    return undef if !$s->{remote};
+    join('', map {$_ if $s->{attr}{$_}} qw/r h s a d n/);
+}
+
+=head4 C<< $f->set_time($t) >>
+
+Set modification and access time of the file to C<$t>.
+
+C<$t> must be in Epoch time (number of seconds since 1970/1/1).
+
+=cut
+sub set_time {
+    my ($s, $t) = @_;
+    utime $t, $t, $s->localpath;
+}
+
+=head4 C<< $f->md5 >>
+
+Return md5 sum of the file.
+
+The result is cached.
+
+=cut
+sub md5 {
+    my $s = shift;
+
+    if (!$s->{md5}) {
+        open my $h, '<', $s->localpath() or die "can't read ".$s->localpath.": $!";
+        binmode $h;
+        $s->{md5} = Digest::MD5->new->addfile($h)->hexdigest;
+        close $h;
+    }
+
+    return $s->{md5};
+}
+
+=head4 C<< $f->put_remote($path, [$abs]) >>
+
+Transfers an existing local file to the server at C<$DIR/$path>.
+The local file is removed on success.
+
+=cut
+sub put_remote
+{
+	my ($s, $path, $abs) = @_;
+	my ($file, $dir) = fileparse($path);
+	my $remote_path = "";
+
+	return undef if $s->{remote};
+
+	$dir = '' if $dir eq './';
+	my $loc;
+
+	if ($abs) {
+		$loc = cleanpath($main::LOCALPATH.'/'.$dir);
+	} else {
+		$dir = cleanpath($main::DIR.'/'.$dir);
+		$loc = cleanpath($main::LOCALPATH.'/'.$dir);
+	}
+
+	if ($dir) {
+		foreach (split("\/", $dir)) {
+			$remote_path .= $_ . "\\";
+			main::smb_client('-c', qq{mkdir "$remote_path"});
+		}
+	}
+
+	$remote_path .= $file;
+	my $local_path = $s->localpath();
+	main::smb_client('-c', qq{put "$local_path" "$remote_path"});
+
+	$s->{dir} = $dir;
+	$s->{name} = $file;
+	$s->{remote} = 1;
+
+	return 0;
+}
+
+=head4 C<< $f->del_remote() >>
+
+Delete remote file.
+
+=cut
+sub del_remote
+{
+	my ($s, $rm_parent_dirs) = @_;
+
+	return 1 unless $s->{remote};
+
+	my $remote_path = $s->smb_remotepath;
+
+	main::smb_client('-c', qq{rm "$remote_path"});
+
+	unless ($rm_parent_dirs) {
+		return 0;
+	}
+
+	# attempt to remove all parent dirs
+	my @cmps = split(/\\/, $s->smb_remotedir);
+	while (scalar(@cmps) > 0) {
+		my $path = join("\\", @cmps);
+		main::smb_client('-c', qq{rmdir "$path"});
+		pop(@cmps);
+	}
+
+	return 0;
+}
+
+sub DESTROY {
+    my $s = shift;
+    if ($s->{delete_on_destruction} && -f $s->localpath) {
+        if ($main::DEBUG) {
+            say "DESTROY ".$s->localpath;
+        }
+        unlink $s->localpath;
+    }
+}
+
+=head3 Functions
+
+=head4 C<< File::walk( \&function, @files) >>
+
+=head4 C<< File::walk( sub { ... }, @files) >>
+
+Iterate on file hierachy in C<@files> and return accumulated results.
+
+Use C<$_> in the sub to access the current C<File>.
+
+The C<@files> must come from a call to the C<File::tree> function.
+
+=cut
+sub walk {
+    my $fun = \&{shift @_};
+
+    my @res;
+
+    for (@_) {
+        if ($_->{attr}{d}) {
+            push @res, walk($fun, @{$_->{content}});
+        } else {
+            push @res, $fun->($_);
+        }
+    }
+
+    return @res;
+}
+
+=head4 C<< File::list( $remotepath ) >>
+
+Return list of file (C<File> instance) in C<$remotepath>.
+
+C<$remotepath> must be a directory.
+
+=cut
+sub list {
+    my ($path) = @_;
+    $path ||= '/';
+    my @files;
+    my $out = main::smb_client('-D', $path, '-c', 'ls');
+    $path =~ s{^/}{};
+
+    for (split /\n/, $out) {
+        next if !/^  (.+?)\s+([AHSRDN]*)\s+(\d+)\s+(.+)/o;
+        my ($fn, $attr, $size, $date) = ($1, $2, $3, $4);
+        next if $fn =~ /^\.{1,2}$/;
+
+        push @files, bless {
+            'remote' => 1,
+            'dir'    => $path,
+            'name'   => $fn,
+            'size'   => int($size),
+            'date'   => $date,
+            'attr'   => {
+                # list context returns something different than the
+                # boolean matching result => force scalar context
+                'a' => scalar ($attr =~ /A/),
+                'h' => scalar ($attr =~ /H/),
+                's' => scalar ($attr =~ /S/),
+                'r' => scalar ($attr =~ /R/),
+                'd' => scalar ($attr =~ /D/),
+                'n' => scalar ($attr =~ /N/),
+            },
+        }, 'File';
+    }
+    return @files;
+}
+
+=head4 C<< File::tree( $remotepath ) >>
+
+Return recursive list of file in C<$remotepath>.
+
+C<$remotepath> must be a directory.
+
+Use C<File::walk()> to iterate over all the files.
+
+=cut
+sub tree {
+    my ($d) = @_;
+    my @files;
+
+    if (!defined $d) {
+        @files = list();
+    } elsif (blessed $d) {
+        @files = list($d->remotepath);
+    } else {
+        @files = list($d);
+    }
+
+    for my $f (@files) {
+        if ($f->{attr}{d}) {
+            $f->{content} = [tree($f)];
+        }
+    }
+
+    return @files;
+}
+
+# remove trailing or duplicated slash
+sub cleanpath {
+    my $p = shift;
+    $p =~ s{/+}{/}g;
+    $p =~ s{/$}{};
+    $p;
+}
+
+# create random file at path local path $fn
+sub create_file {
+    my ($fn, $size) = @_;
+    my $buf = '';
+    unlink $fn if -e $fn;
+    $size ||= main::random(512, 1024);
+    $size = int($size);
+    my $md5;
+
+    # try /dev/urandom, faster
+    if (-e '/dev/urandom') {
+        my $cmd = sprintf('head -c %d /dev/urandom | tee %s | md5sum',
+                          $size, quotemeta($fn));
+        $md5 = (split / /, `$cmd`)[0];
+    } else {
+        open my $out, '>', $fn or die "can't open $fn: $!\n";
+        binmode $out;
+        for (1..$size) {
+            $buf .= pack('C', main::random(0, 256));
+        }
+        print $out $buf;
+        close $out;
+        $md5 = md5_hex($buf);
+    }
+    return $md5;
+}
+
+
+=head3 Examples
+
+    # create remote file in $DIR/foo/bar
+        my $f = File->new_remote("foo/bar/myfile");
+        say $f->localpath;  # /opt/share/$DIR/foo/bar/myfile
+        say $f->remotepath; # $DIR/foo/bar/myfile
+        say $f->remotedir;  # $DIR/foo/bar
+
+
+    # same but in root dir
+        my $f = File->new_remote("myfile", 1);
+        say $f->localpath;  # /opt/share/myfile
+        say $f->remotepath; # myfile
+        say $f->remotedir;  #
+
+
+    # create local random temp file in $TMP
+        my $f = File->new_local("$TMP/temp");
+        say $f->remotepath; # undef because it's not on the server
+
+
+    # same but file contains "hello"
+        my $f = File->new_local("$TMP/temp", "hello");
+
+
+    # list of files in $DIR (1 level)
+        for (File::list($DIR)) {
+            say $_->remotepath;
+        }
+
+
+    # list of all files in dir and subdir of $DIR
+        File::walk(sub { say $_->remotepath }, File::tree($DIR));
+
+=cut
+
+1;
diff --git a/source3/selftest/tests.py b/source3/selftest/tests.py
index 3a6186c..c6ba2fe 100755
--- a/source3/selftest/tests.py
+++ b/source3/selftest/tests.py
@@ -242,6 +242,28 @@ for env in ["fileserver"]:
                        '-d', '$PREFIX', '-b', smbclient3,
                        '--subunit', '--', configuration])
 
+    # Test smbcacls
+    smbcacls_test_args = [os.path.join(samba3srcdir, "script/tests/test_smbcacls.pl"),
+			  '-n', '$SERVER', '-s', 'tmp', '-u', '$USERNAME',
+			  '-p', '$PASSWORD', '-l', '$LOCAL_PATH', '-d', '$PREFIX',
+			  '--cli_bin=' + smbclient3, '-cacls_bin=' + smbcacls,
+			  '--subunit', '--clean']
+    smbcacls_subtests = ["file_set", "file_mod", "file_del", "file_add",
+			 "oi_add", "oi_del", "oi_mod",
+			 "ci_add", "ci_del", "ci_mod",
+			 "coi_add", "coi_del", "coi_mod",
+			 "inherit_set", "coi_set", "ci_set",
+			 "coinp_add", "oinp_add", "cinp_add",
+			 "coinp_delete", "oinp_delete", "cinp_delete",
+			 "coi_inhibit"]
+
+    i = 0
+    for cacls_subtest in smbcacls_subtests:
+	    plantestsuite("samba3.blackbox.smbcacls." + cacls_subtest + " (%s)"
+			  % env, env, smbcacls_test_args
+			  + ['--test', str(i), '--', configuration])
+	    i += 1
+
 #TODO encrypted against member, with member creds, and with DC creds
 plantestsuite("samba3.blackbox.net.misc", "nt4_dc:local",
               [os.path.join(samba3srcdir, "script/tests/test_net_misc.sh"),
-- 
2.10.2


From 64874618d86a1eb38f14743ce7f8c2e1252f463a Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Thu, 14 Nov 2013 17:45:07 +0000
Subject: [PATCH 2/4] add new '--propagate-inheritance' option for smbcacls

smbcacls now can take a '--propagate-inheritance' flag to indicate that the
add, delete, modify and set operations now support automatic propagation of
inheritable ACE(s)

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

diff --git a/source3/utils/smbcacls.c b/source3/utils/smbcacls.c
index 255ff97..0a687c8 100644
--- a/source3/utils/smbcacls.c
+++ b/source3/utils/smbcacls.c
@@ -6,6 +6,7 @@
    Copyright (C) Tim Potter      2000
    Copyright (C) Jeremy Allison  2000
    Copyright (C) Jelmer Vernooij 2003
+   Copyright (C) Noel Power <noel.power at suse.com> 2013
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -33,10 +34,13 @@
 #include "../librpc/gen_ndr/ndr_lsa_c.h"
 #include "util_sd.h"
 
+static char DIRSEP_CHAR = '\\';
+
 static int test_args;
 
 #define CREATE_ACCESS_READ READ_CONTROL_ACCESS
 
+static int inheritance = 0;
 static int sddl;
 static int query_sec_info = -1;
 static int set_sec_info = -1;
@@ -47,6 +51,18 @@ enum acl_mode {SMB_ACL_SET, SMB_ACL_DELETE, SMB_ACL_MODIFY, SMB_ACL_ADD };
 enum chown_mode {REQUEST_NONE, REQUEST_CHOWN, REQUEST_CHGRP, REQUEST_INHERIT};
 enum exit_values {EXIT_OK, EXIT_FAILED, EXIT_PARSE_ERROR};
 
+struct cacl_callback_state {
+	struct user_auth_info *auth_info;
+	struct cli_state *cli;
+	struct security_descriptor *aclsd;
+	struct security_acl *acl_to_add;
+	enum acl_mode mode;
+	char *the_acl;
+	bool acl_no_propagate;
+	bool inherit_enabled;
+	bool numeric;
+};
+
 static NTSTATUS cli_lsa_lookup_domain_sid(struct cli_state *cli,
 					  struct dom_sid *sid)
 {
@@ -123,12 +139,14 @@ static struct dom_sid *get_domain_sid(struct cli_state *cli)
 }
 
 /* add an ACE to a list of ACEs in a struct security_acl */
-static bool add_ace(struct security_acl **the_acl, struct security_ace *ace)
+static bool add_ace_with_ctx(TALLOC_CTX *ctx, struct security_acl **the_acl,
+			     struct security_ace *ace)
+
 {
 	struct security_acl *new_ace;
 	struct security_ace *aces;
 	if (! *the_acl) {
-		return (((*the_acl) = make_sec_acl(talloc_tos(), 3, 1, ace))
+		return (((*the_acl) = make_sec_acl(ctx, 3, 1, ace))
 			!= NULL);
 	}
 
@@ -138,12 +156,18 @@ static bool add_ace(struct security_acl **the_acl, struct security_ace *ace)
 	memcpy(aces, (*the_acl)->aces, (*the_acl)->num_aces * sizeof(struct
 	security_ace));
 	memcpy(aces+(*the_acl)->num_aces, ace, sizeof(struct security_ace));
-	new_ace = make_sec_acl(talloc_tos(),(*the_acl)->revision,1+(*the_acl)->num_aces, aces);
+	new_ace = make_sec_acl(ctx, (*the_acl)->revision,
+			       1+(*the_acl)->num_aces, aces);
 	SAFE_FREE(aces);
 	(*the_acl) = new_ace;
 	return True;
 }
 
+static bool add_ace(struct security_acl **the_acl, struct security_ace *ace)
+{
+	return add_ace_with_ctx(talloc_tos(), the_acl, ace);
+}
+
 /* parse a ascii version of a security descriptor */
 static struct security_descriptor *sec_desc_parse(TALLOC_CTX *ctx, struct cli_state *cli, char *str)
 {
@@ -250,7 +274,9 @@ static uint16_t get_fileinfo(struct cli_state *cli, const char *filename)
 /*****************************************************
 get sec desc for filename
 *******************************************************/
-static struct security_descriptor *get_secdesc(struct cli_state *cli, const char *filename)
+static struct security_descriptor *get_secdesc_with_ctx(TALLOC_CTX *ctx,
+							struct cli_state *cli,
+							const char *filename)
 {
 	uint16_t fnum = (uint16_t)-1;
 	struct security_descriptor *sd;
@@ -284,7 +310,7 @@ static struct security_descriptor *get_secdesc(struct cli_state *cli, const char
 	}
 
 	status = cli_query_security_descriptor(cli, fnum, sec_info,
-					       talloc_tos(), &sd);
+					       ctx, &sd);
 
 	cli_close(cli, fnum);
 
@@ -296,6 +322,11 @@ static struct security_descriptor *get_secdesc(struct cli_state *cli, const char
         return sd;
 }
 
+static struct security_descriptor *get_secdesc(struct cli_state *cli,
+					       const char *filename)
+{
+	return get_secdesc_with_ctx(talloc_tos(), cli, filename);
+}
 /*****************************************************
 set sec desc for filename
 *******************************************************/
@@ -485,23 +516,18 @@ static void sort_acl(struct security_acl *the_acl)
 }
 
 /***************************************************** 
-set the ACLs on a file given an ascii description
+set the ACLs on a file given a security descriptor
 *******************************************************/
 
-static int cacl_set(struct cli_state *cli, const char *filename,
-		    char *the_acl, enum acl_mode mode, bool numeric)
+static int cacl_set_from_sd(struct cli_state *cli, const char *filename,
+			    struct security_descriptor *sd, enum acl_mode mode,
+			    bool numeric)
 {
-	struct security_descriptor *sd, *old;
+	struct security_descriptor *old;
 	uint32_t i, j;
 	size_t sd_size;
 	int result = EXIT_OK;
 
-	if (sddl) {
-		sd = sddl_decode(talloc_tos(), the_acl, get_domain_sid(cli));
-	} else {
-		sd = sec_desc_parse(talloc_tos(), cli, the_acl);
-	}
-
 	if (!sd) return EXIT_PARSE_ERROR;
 	if (test_args) return EXIT_OK;
 
@@ -773,6 +799,633 @@ static struct cli_state *connect_one(const struct user_auth_info *auth_info,
 	return c;
 }
 
+/*
+ * Process mntpoint and ensure resulting combination of mntpoint, mask & fname
+ * is terminated with wildcard
+ */
+static char *build_dirname(TALLOC_CTX *ctx, const char *mntpoint,
+	const char *mask, char *dir, char *fname)
+{
+	char *mask2 = NULL;
+	char *p = NULL;
+
+	mask2 = talloc_asprintf(ctx,
+			"%s%s",
+			mntpoint,
+			mask);
+	if (!mask2) {
+		return NULL;
+	}
+	p = strrchr_m(mask2, DIRSEP_CHAR);
+	if (p) {
+		p[1] = 0;
+	} else {
+		mask2[0] = '\0';
+	}
+	mask2 = talloc_asprintf_append(mask2,
+				"%s\\*",
+				fname);
+	return mask2;
+}
+
+/*
+ * Returns the a copy of the ACL flags in ace modified according
+ * to some inheritance rules.
+ *   a) SEC_ACE_FLAG_INHERITED_ACE is propagated to children
+ *   b) SEC_ACE_FLAG_INHERIT_ONLY is set on container children for OI (only)
+ *   c) SEC_ACE_FLAG_OBJECT_INHERIT & SEC_ACE_FLAG_CONTAINER_INHERIT are
+ *      stripped from flags to be propagated to non-container children
+ *   d) SEC_ACE_FLAG_OBJECT_INHERIT & SEC_ACE_FLAG_CONTAINER_INHERIT are
+ *      stripped from flags to be propagated if the NP flag
+ *      SEC_ACE_FLAG_NO_PROPAGATE_INHERIT is present
+ */
+
+static uint8_t get_flags_to_propagate(bool is_container,
+				struct security_ace *ace)
+{
+	uint8_t newflags = ace->flags;
+	/* OBJECT inheritance */
+	bool acl_objinherit = (ace->flags &
+		SEC_ACE_FLAG_OBJECT_INHERIT) == SEC_ACE_FLAG_OBJECT_INHERIT;
+	/* CONTAINER inheritance */
+	bool acl_cntrinherit = (ace->flags &
+		SEC_ACE_FLAG_CONTAINER_INHERIT) ==
+			SEC_ACE_FLAG_CONTAINER_INHERIT;
+	/* PROHIBIT inheritance */
+	bool prohibit_inheritance = ((ace->flags &
+		SEC_ACE_FLAG_NO_PROPAGATE_INHERIT) ==
+			SEC_ACE_FLAG_NO_PROPAGATE_INHERIT);
+
+	/* all children need to have the SEC_ACE_FLAG_INHERITED_ACE set */
+	if (acl_cntrinherit || acl_objinherit) {
+		newflags |= SEC_ACE_FLAG_INHERITED_ACE;
+		/*
+		 * object inherit ( alone ) on a container needs
+		 * SEC_ACE_FLAG_INHERIT_ONLY
+		 */
+		if (is_container && acl_objinherit &&
+			!acl_cntrinherit) {
+			newflags |= SEC_ACE_FLAG_INHERIT_ONLY;
+		}
+		if (!is_container || prohibit_inheritance) {
+			/*
+			 * don't apply object/container inheritance flags to
+			 * non dirs or if instructed not to propagate
+			 */
+			newflags &= ~(SEC_ACE_FLAG_OBJECT_INHERIT
+					| SEC_ACE_FLAG_CONTAINER_INHERIT
+					| SEC_ACE_FLAG_INHERIT_ONLY);
+			/*
+			 * don't propagate INHERIT_ONLY (from parent) either */
+			if (!is_container) {
+				newflags &= ~SEC_ACE_FLAG_INHERIT_ONLY;
+			}
+			if (prohibit_inheritance) {
+				newflags &= ~SEC_ACE_FLAG_NO_PROPAGATE_INHERIT;
+			}
+		}
+	} else {
+		newflags &= ~SEC_ACE_FLAG_INHERITED_ACE;
+	}
+	return newflags;
+}
+
+/*
+ * This function builds a new acl for 'caclfile', first it removes any
+ * existing inheritable ace(s) from the current acl of caclfile, secondly it
+ * applies any inheritable acls of the parent of caclfile ( inheritable acls of
+ * caclfile's parent are passed via acl_to_add member of cbstate )
+ *
+ */
+static NTSTATUS propagate_inherited_aces(char *caclfile,
+			struct cacl_callback_state *cbstate)
+{
+	TALLOC_CTX *aclctx;
+	NTSTATUS status = NT_STATUS_OK;
+	int result;
+	int fileattr = 0;
+	struct security_descriptor *old;
+	bool is_container = false;
+	struct security_acl *acl_to_add = cbstate->acl_to_add;
+	struct security_acl *explicit_aces = NULL;
+	uint32_t i, j;
+
+	aclctx = talloc_new(NULL);
+	if (!aclctx) {
+		status = NT_STATUS_NO_MEMORY;
+		goto out;
+	}
+	old = get_secdesc_with_ctx(aclctx, cbstate->cli, caclfile);
+
+	if (!old) {
+		status = NT_STATUS_UNSUCCESSFUL;
+		goto out;
+	}
+
+	/* inhibit propagation? */
+	if (old->type & SEC_DESC_DACL_PROTECTED) {
+		status = NT_STATUS_OK;
+		goto out;
+	}
+
+	fileattr = get_fileinfo(cbstate->cli, caclfile);
+	is_container = (fileattr & FILE_ATTRIBUTE_DIRECTORY);
+
+	/* find explict ace(s) */
+	for (j = 0; old->dacl && j < old->dacl->num_aces; j++) {
+
+		if (!(old->dacl->aces[j].flags & SEC_ACE_FLAG_INHERITED_ACE)) {
+			if (!add_ace_with_ctx(aclctx, &explicit_aces,
+					      &old->dacl->aces[j])) {
+				status = NT_STATUS_NO_MEMORY;
+				goto out;
+			}
+		}
+	}
+
+	/* remove all old ace(s) from security descriptor */
+	old->dacl->num_aces = 0;
+
+        /* add the explict aces to security descriptor */
+	for (i = 0;explicit_aces && i < explicit_aces->num_aces; i++) {
+		struct security_ace ace = explicit_aces->aces[i];
+		if (!add_ace_with_ctx(aclctx, &old->dacl, &ace)) {
+			status = NT_STATUS_NO_MEMORY;
+			goto out;
+		}
+	}
+
+	/* propagate any inheritable ace(s) to be added */
+	for (i = 0; acl_to_add && i < acl_to_add->num_aces; i++) {
+		struct security_ace ace = acl_to_add->aces[i];
+		bool is_objectinherit = (ace.flags &
+			SEC_ACE_FLAG_OBJECT_INHERIT) ==
+				SEC_ACE_FLAG_OBJECT_INHERIT;
+		bool is_inherited = false;
+		/* don't propagate flags to a file unless OI */
+		if (!is_objectinherit && !is_container) {
+			continue;
+		}
+		/*
+		 * adjust flags according to inheritance
+		 * rules
+		 */
+		ace.flags = get_flags_to_propagate(is_container, &ace);
+		is_inherited = (ace.flags &
+			SEC_ACE_FLAG_INHERITED_ACE) ==
+				SEC_ACE_FLAG_INHERITED_ACE;
+		/* don't propagate non inherited flags */
+		if (!is_inherited) {
+			continue;
+		}
+		if (!add_ace_with_ctx(aclctx, &old->dacl, &ace)) {
+			status = NT_STATUS_NO_MEMORY;
+			goto out;
+		}
+	}
+
+	result = cacl_set_from_sd(cbstate->cli, caclfile,
+				  old,
+				  SMB_ACL_SET, cbstate->numeric);
+	if (result  != EXIT_OK) {
+		status = NT_STATUS_UNSUCCESSFUL;
+		goto out;
+	}
+
+out:
+	TALLOC_FREE(aclctx);
+	return status;
+}
+
+/*
+ * Returns true if 'ace' contains SEC_ACE_FLAG_OBJECT_INHERIT or
+ * SEC_ACE_FLAG_CONTAINER_INHERIT, if inherited_only is true then
+ * additionally SEC_ACE_FLAG_INHERITED_ACE must be set
+ */
+static bool is_inheritable_ace(struct security_ace *ace, bool inherited_only)
+{
+	uint8_t flags = ace->flags;
+
+	flags &= (SEC_ACE_FLAG_OBJECT_INHERIT
+			| SEC_ACE_FLAG_CONTAINER_INHERIT
+			| SEC_ACE_FLAG_INHERITED_ACE);
+	if (inherited_only) {
+		/* only consider (OI) and/or (CI) flags WITH (I) */
+		return  (flags & SEC_ACE_FLAG_INHERITED_ACE);
+	}
+	return (flags > 0);
+}
+
+/* This method does some basic sanity checking with respect to automatic
+ * inheritance. e.g. it checks if it is possible to do a set, it detects illegal
+ * attempts to set inherited permissions directly. Additionally this method
+ * does some basic initialisation for instance it parses the ACL passed on the
+ * command line.
+ */
+static NTSTATUS prepare_inheritance_propagation(TALLOC_CTX *ctx, char *filename,
+			struct cacl_callback_state *cbstate)
+{
+	NTSTATUS result = NT_STATUS_OK;
+	char *the_acl = cbstate->the_acl;
+	struct cli_state *cli = cbstate->cli;
+	enum acl_mode mode = cbstate->mode;
+	struct security_descriptor *sd = NULL;
+	struct security_descriptor *old = get_secdesc_with_ctx(ctx, cli,
+							      filename);
+	uint32_t j;
+	bool propagate = false;
+
+	if (!old) {
+		result = NT_STATUS_UNSUCCESSFUL;
+		goto out;
+	}
+
+	/* parse acl passed on the command line */
+	if (sddl) {
+		cbstate->aclsd = sddl_decode(ctx, the_acl,
+					     get_global_sam_sid());
+	} else {
+		cbstate->aclsd = sec_desc_parse(ctx, cli, the_acl);
+	}
+
+	if (!cbstate->aclsd) {
+		result = NT_STATUS_UNSUCCESSFUL;
+		goto out;
+	}
+
+	/* need to do inheritance propagation related bits? */
+	if (cbstate->acl_no_propagate) {
+		result = NT_STATUS_OK;
+		goto out;
+	}
+
+	sd = cbstate->aclsd;
+
+	cbstate->inherit_enabled = false;
+	if (!(old->type & SEC_DESC_DACL_PROTECTED)) {
+		cbstate->inherit_enabled = true;
+	}
+	/* set operation if inheritance is enabled doesn't make sense */
+	if (mode == SMB_ACL_SET && cbstate->inherit_enabled) {
+		d_printf("Inheritance enabled at %s, can't apply set operation\n",filename);
+		result = NT_STATUS_UNSUCCESSFUL;
+		goto out;
+	}
+
+	/*
+	 * search command line acl for any illegal SEC_ACE_FLAG_INHERITED_ACE
+	 * flags that are set
+	 */
+	for (j = 0; sd->dacl && j < sd->dacl->num_aces; j++) {
+		struct security_ace *ace = &sd->dacl->aces[j];
+		if (ace->flags & SEC_ACE_FLAG_INHERITED_ACE) {
+			d_printf("Illegal paramater %s\n", the_acl);
+			result = NT_STATUS_UNSUCCESSFUL;
+			goto out;
+		}
+		if (!propagate) {
+			if (is_inheritable_ace(ace, false)) {
+				propagate = true;
+			}
+		}
+	}
+out:
+	cbstate->acl_no_propagate = !propagate;
+	return result;
+}
+
+/*
+ * This method builds inheritable ace(s) from filename (which should be
+ * a container) that need propagating to children in order to provide
+ * automatic inheritance. Those inheritably ace(s) are stored in
+ * acl_to_add member of cbstate for later processing
+ * (see propagate_inherited_aces)
+ */
+static NTSTATUS get_parents_inheritable_aces(TALLOC_CTX *ctx, char *filename,
+			struct cacl_callback_state *cbstate,
+			bool filter_inherited_only)
+{
+	NTSTATUS result = NT_STATUS_OK;
+	struct cli_state *cli = cbstate->cli;
+	struct security_descriptor *sd = get_secdesc_with_ctx(ctx, cli,
+							      filename);
+	struct security_acl *acl_to_add = NULL;
+	int j;
+	if (!sd) {
+		result = NT_STATUS_UNSUCCESSFUL;
+		goto out; 
+	}
+	/*
+	 * Check if any inheritance related flags are used, if not then
+	 * nothing to do. At the same time populate acls for inheritance
+	 * related ace(s) that need to be added to or deleted from children as
+	 * a result of inheritance propagation.
+	 */
+
+	for (j = 0; sd->dacl && j < sd->dacl->num_aces; j++) {
+		struct security_ace *ace = &sd->dacl->aces[j];
+		if (is_inheritable_ace(ace, filter_inherited_only)) {
+			bool added = add_ace_with_ctx(ctx, &acl_to_add, ace);
+			if (!added) {
+				result = NT_STATUS_NO_MEMORY;
+				goto out;
+			}
+		}
+	}
+	cbstate->acl_to_add = acl_to_add;
+out:
+	return result;
+}
+
+/*
+ * Callback handler to handle child elements processed by cli_list,  we attempt
+ * to propagate inheritable ace(s) to each child via the function
+ * propagate_inherited_aces. Children that are themselves directories are passed
+ * to cli_list again ( to decend the directory structure )
+ */
+static NTSTATUS cacl_set_cb(const char *mntpoint, struct file_info *f,
+			   const char *mask, void *state)
+{
+	struct cacl_callback_state *cbstate =
+		(struct cacl_callback_state *)state;
+	struct cli_state *cli = cbstate->cli;
+	struct user_auth_info *auth_info = cbstate->auth_info;
+
+	TALLOC_CTX *dirctx;
+	NTSTATUS status = NT_STATUS_OK;
+
+	char *dir = NULL;
+	char *dir_end = NULL;
+	char *mask2 = NULL;
+	char *targetpath = NULL;
+	char *caclfile = NULL;
+
+	dirctx = talloc_new(NULL);
+	if (!dirctx) {
+		status = NT_STATUS_NO_MEMORY;
+		goto out;
+	}
+	/* Work out the directory. */
+	dir = talloc_strdup(dirctx, mask);
+	if (!dir) {
+		status = NT_STATUS_NO_MEMORY;
+		goto out;
+	}
+
+	dir_end = strrchr(dir, DIRSEP_CHAR);
+	if (dir_end != NULL) {
+		*dir_end = '\0';
+	}
+
+	if (f->mode & FILE_ATTRIBUTE_DIRECTORY) {
+		struct cli_state *targetcli = NULL;
+		struct cacl_callback_state dir_cbstate;
+		uint16_t attribute = FILE_ATTRIBUTE_DIRECTORY
+			| FILE_ATTRIBUTE_SYSTEM
+			| FILE_ATTRIBUTE_HIDDEN;
+		dir_end = NULL;
+
+		if (!f->name || !f->name[0]) {
+			d_printf("Empty dir name returned. Possible server misconfiguration.\n");
+			status = NT_STATUS_UNSUCCESSFUL;
+			goto out;
+		}
+
+		/* ignore special '.' & '..' */
+		if (!f->name || strequal(f->name, ".") ||
+			strequal(f->name, "..")) {
+			status = NT_STATUS_OK;
+			goto out;
+		}
+
+		mask2 = build_dirname(dirctx, mntpoint, mask, dir, f->name);
+		if (mask2 == NULL) {
+			status = NT_STATUS_NO_MEMORY;
+			goto out;
+		}
+
+		/* check for dfs */
+		status = cli_resolve_path(dirctx, "", auth_info, cli,
+			mask2, &targetcli, &targetpath);
+		if (!NT_STATUS_IS_OK(status)) {
+			goto out;
+		}
+
+		/*
+		 * prepare path to caclfile, remove any existing wildcard
+		 * chars and convert path separators.
+		 */
+
+		caclfile = talloc_strdup(dirctx, targetpath);
+		if (!caclfile) {
+			status = NT_STATUS_NO_MEMORY;
+			goto out;
+		}
+		dir_end = strrchr(caclfile, '*');
+		if (dir_end != NULL) {
+			*dir_end = '\0';
+		}
+
+		string_replace(caclfile, '/', '\\');
+
+		/* attempt to propagate any inherited ace to child */
+		status = propagate_inherited_aces(caclfile, cbstate);
+		if (!NT_STATUS_IS_OK(status)) {
+			goto out;
+		}
+
+		/* get acls from parent (e.g. caclfile) */
+		dir_cbstate = *cbstate;
+		status = get_parents_inheritable_aces(dirctx, caclfile,
+						      &dir_cbstate, false);
+		if (!NT_STATUS_IS_OK(status)) {
+			goto out;
+		}
+
+		/*
+		 * ensure cacl_set_cb gets called for children
+		 * of the directory (targetpath)
+		 */
+		status = cli_list(targetcli, targetpath,
+			attribute, cacl_set_cb,
+			(void *)&dir_cbstate);
+
+		if (!NT_STATUS_IS_OK(status)) {
+			goto out;
+		}
+
+	} else {
+		/*
+		 * build full path to caclfile and replace '/' with '\' so
+		 * other utility functions can deal with it
+		 */
+
+		caclfile = talloc_asprintf(dirctx, "%s/%s", dir, f->name);
+		if (!caclfile) {
+			status = NT_STATUS_NO_MEMORY;
+			goto out;
+		}
+
+		string_replace(caclfile, '/', '\\');
+
+		/* attempt to propagate any inherited ace to file caclfile */
+		status = propagate_inherited_aces(caclfile, cbstate);
+
+		if (!NT_STATUS_IS_OK(status)) {
+			goto out;
+		}
+	}
+	status = NT_STATUS_OK;
+out:
+	if (!NT_STATUS_IS_OK(status)) {
+		d_printf("error %s: processing %s\n",
+			nt_errstr(status),
+			caclfile);
+	}
+	TALLOC_FREE(dirctx);
+	return status;
+}
+
+
+/*
+ * Wrapper around cl_list to (if required) decend the directory tree pointed
+ * to by 'filename', helper callback function 'cacl_set_cb' handles the child
+ * elements processed  by cli_list. A smbcacls without '--propagate-inheritance'
+ * won't call cli_list (inhibited by cacl_callback_state.acl_no_propagate
+ */
+static int cacl_set(char *filename,
+			struct cacl_callback_state *cbstate)
+{
+	int result = EXIT_OK;
+	NTSTATUS ntstatus;
+	int fileattr = 0;
+	char *targetpath = NULL;
+	char *mask = NULL;
+	struct cli_state *cli = cbstate->cli;
+	struct cli_state *targetcli = NULL;
+	struct user_auth_info *auth_info = cbstate->auth_info;
+	TALLOC_CTX *ctx;
+	bool isdirectory = false;
+	uint16_t attribute = FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM
+				| FILE_ATTRIBUTE_HIDDEN;
+	ctx = talloc_new(NULL);
+	if (!ctx) {
+		result = EXIT_FAILED;
+		goto out;
+	}
+	/* ensure we have a filename that starts with '\' */
+	if (!filename || *filename != DIRSEP_CHAR) {
+		/* illegal or no filename */
+		result = EXIT_FAILED;
+		d_printf("illegal or missing name '%s'\n", filename);
+		goto out;
+	}
+
+	fileattr = get_fileinfo(cli, filename);
+	isdirectory = (fileattr & FILE_ATTRIBUTE_DIRECTORY)
+		== FILE_ATTRIBUTE_DIRECTORY;
+
+	/*
+	 * if we've got as far as here then we have already evaluated
+	 * the args.
+	 */
+	if (test_args) {
+		goto out;
+	}
+
+	mask = NULL;
+	/* make sure we have a trailing '\*' for directory */
+	if (!isdirectory) {
+		mask = talloc_strdup(ctx, filename);
+	} else if (strlen(filename) > 1) {
+		/*
+		 * if the passed file name doesn't have a trailing '\'
+		 * append it.
+		 */
+		char *name_end = strrchr(filename, DIRSEP_CHAR);
+		if (name_end != filename + strlen(filename) + 1) {
+			mask = talloc_asprintf(ctx, "%s\\*", filename);
+		} else {
+			mask = talloc_strdup(ctx, filename);
+		}
+	} else {
+		/* filename is a single '\', just append '*' */
+		mask = talloc_asprintf_append(mask, "%s*", filename);
+	}
+
+	if (!mask) {
+		result = EXIT_FAILED;
+		goto out;
+	}
+
+	/*
+	 * prepare for automatic propagation of the acl passed on the
+	 * cmdline.
+	 */
+	ntstatus = prepare_inheritance_propagation(ctx, filename,
+						   cbstate);
+	if (!NT_STATUS_IS_OK(ntstatus)) {
+		goto out;
+	}
+
+	/* check for dfs */
+	ntstatus = cli_resolve_path(ctx, "", auth_info, cli, mask, &targetcli,
+		&targetpath);
+	if (!NT_STATUS_IS_OK(ntstatus)) {
+		goto out;
+	}
+
+	/*
+	 * it seems that if inheritance isn't enabled then we don't need
+	 * to take into account the parent's inheritable inherited ace(s)
+	 */
+
+	if (cbstate->inherit_enabled) {
+		/* get only inherited inheritable flags (I) */
+		ntstatus = get_parents_inheritable_aces(ctx, filename,
+							cbstate, true);
+		if (!NT_STATUS_IS_OK(ntstatus)) {
+			goto out;
+		}
+	}
+
+	result = cacl_set_from_sd(cli, filename, cbstate->aclsd,
+				cbstate->mode, cbstate->numeric);
+
+	/*
+	 * strictly speaking it could be considered an error if a file was
+	 * specificied with '--propagate-inheritance'. However we really want
+	 * to eventually get rid of '--propagate-inheritance' so we will be
+	 * more forgiving here and instead just exit early.
+	 */
+	if (!isdirectory || (result != EXIT_OK)) {
+		goto out;
+	}
+
+	/* check if there is actually any need to propagate */
+	if (cbstate->acl_no_propagate) {
+		goto out;
+	}
+
+	/* get inheritable attributes from parent (e.g. filename) */
+	ntstatus = get_parents_inheritable_aces(ctx, filename, cbstate, false);
+	if (!NT_STATUS_IS_OK(ntstatus)) {
+		goto out;
+	}
+	/* process children */
+	ntstatus = cli_list(targetcli, targetpath, attribute,
+			cacl_set_cb,
+			(void *)cbstate);
+out:
+	if (!NT_STATUS_IS_OK(ntstatus)) {
+		d_printf("error: %s processing %s\n",
+			 nt_errstr(ntstatus), targetpath);
+		result = EXIT_FAILED;
+	}
+	TALLOC_FREE(ctx);
+	return result;
+}
+
 /****************************************************************************
   main program
 ****************************************************************************/
@@ -802,6 +1455,7 @@ int main(int argc, char *argv[])
 		{ "chgrp", 'G', POPT_ARG_STRING, NULL, 'G', "Change group ownership of a file", "GROUPNAME" },
 		{ "inherit", 'I', POPT_ARG_STRING, NULL, 'I', "Inherit allow|remove|copy" },
 		{ "numeric", 0, POPT_ARG_NONE, &numeric, 1, "Don't resolve sids or masks to names" },
+		{ "propagate-inheritance", 0, POPT_ARG_NONE, &inheritance, 1, "Supports propagation of inheritable ACE(s) when used in conjunction with add, delete, set or modify" },
 		{ "sddl", 0, POPT_ARG_NONE, &sddl, 1, "Output and input acls in sddl format" },
 		{ "query-security-info", 0, POPT_ARG_INT, &query_sec_info, 1,
 		  "The security-info flags for queries"
@@ -879,8 +1533,11 @@ int main(int argc, char *argv[])
 			break;
 		}
 	}
+	if (inheritance && !the_acl) {
+		poptPrintUsage(pc, stderr, 0);
+		return -1;
+	}
 
-	/* Make connection to server */
 	if(!poptPeekArg(pc)) {
 		poptPrintUsage(pc, stderr, 0);
 		return -1;
@@ -919,6 +1576,7 @@ int main(int argc, char *argv[])
 	*share = 0;
 	share++;
 
+	/* Make connection to server */
 	if (!test_args) {
 		cli = connect_one(cmdline_auth_info, server, share);
 		if (!cli) {
@@ -945,7 +1603,21 @@ int main(int argc, char *argv[])
 	} else if (change_mode != REQUEST_NONE) {
 		result = owner_set(cli, change_mode, filename, owner_username);
 	} else if (the_acl) {
-		result = cacl_set(cli, filename, the_acl, mode, numeric);
+		struct cacl_callback_state cbstate;
+		cbstate.auth_info = cmdline_auth_info;
+		cbstate.cli = cli;
+		cbstate.aclsd = NULL;
+		cbstate.acl_to_add = NULL;
+		cbstate.mode = mode;
+		cbstate.the_acl = the_acl;
+		cbstate.inherit_enabled = false;
+		cbstate.numeric = numeric;
+		if (inheritance) {
+			cbstate.acl_no_propagate = false;
+		} else {
+			cbstate.acl_no_propagate = true;
+		}
+		result = cacl_set(filename, &cbstate);
 	} else {
 		result = cacl_dump(cli, filename, numeric);
 	}
-- 
2.10.2


From 811d410dd281c625a66a6407a6f91a6c1a5ccf1b Mon Sep 17 00:00:00 2001
From: David Disseldorp <ddiss at samba.org>
Date: Thu, 14 Nov 2013 19:38:19 +0100
Subject: [PATCH 3/4] doc: describe smbcacls --propagate-inheritance

Signed-off-by: David Disseldorp <ddiss at samba.org>
---
 docs-xml/manpages/smbcacls.1.xml | 51 ++++++++++++++++++++++++++++++----------
 1 file changed, 39 insertions(+), 12 deletions(-)

diff --git a/docs-xml/manpages/smbcacls.1.xml b/docs-xml/manpages/smbcacls.1.xml
index cb198a7..00f4778 100644
--- a/docs-xml/manpages/smbcacls.1.xml
+++ b/docs-xml/manpages/smbcacls.1.xml
@@ -28,6 +28,7 @@
 		<arg choice="opt">-C|--chown name</arg>
 		<arg choice="opt">-G|--chgrp name</arg>
 		<arg choice="opt">-I allow|remove|copy</arg>
+		<arg choice="opt">--propagate-inheritance</arg>
 		<arg choice="opt">--numeric</arg>
 		<arg choice="opt">-t</arg>
 		<arg choice="opt">-U username</arg>
@@ -123,19 +124,23 @@
 		<para>This command is a shortcut for -M GROUP:name.</para></listitem>
 		</varlistentry>
 		
-		
-		
 		<varlistentry>
 		<term>-I|--inherit allow|remove|copy</term>
 		<listitem><para>Set or unset the windows "Allow inheritable
 		permissions" check box using the <parameter>-I</parameter>
 		option.  To set the check box pass allow. To unset the check
 		box pass either remove or copy. Remove will remove all
-		inherited acls. Copy will copy all the inherited acls.
+		inherited ACEs. Copy will copy all the inherited ACEs.
 		</para></listitem>
-
 		</varlistentry>
 
+		<varlistentry>
+		<term>--propagate-inheritance</term>
+		<listitem><para>Add, modify, delete or set ACEs on an entire
+		directory tree according to the inheritance flags. Refer to the
+		INHERITANCE section for details.
+		</para></listitem>
+		</varlistentry>
 
 		<varlistentry>
 		<term>--numeric</term>
@@ -229,18 +234,22 @@ ACL:<sid or name>:<type>/<flags>/<mask>
 	determine the type of access granted to the SID. </para>
 
 	<para>The type can be either ALLOWED or	DENIED to allow/deny access 
-	to the SID. The flags values are generally zero for file ACEs and
-	either 9 or 2 for directory ACEs.  Some common flags are: </para>
+	to the SID.</para>
+
+	<para>The flags field defines how the ACE should be considered when
+	performing inheritance. <command>smbcacls</command> uses these flags
+	when run with <parameter>--propagate-inheritance</parameter>.</para>
+
+	<para>Flags can be specified as decimal or hexadecimal values, or with
+	the respective (XX) aliases, separated by a vertical bar "|".</para>
 
 	<itemizedlist> 
-		<listitem><para><constant>#define SEC_ACE_FLAG_OBJECT_INHERIT     	0x1</constant></para></listitem>
-		<listitem><para><constant>#define SEC_ACE_FLAG_CONTAINER_INHERIT  	0x2</constant></para></listitem>
-		<listitem><para><constant>#define SEC_ACE_FLAG_NO_PROPAGATE_INHERIT     0x4</constant></para></listitem>
-		<listitem><para><constant>#define SEC_ACE_FLAG_INHERIT_ONLY       	0x8</constant></para></listitem>
+		<listitem><para><emphasis>(OI)</emphasis> Object Inherit	0x1</para></listitem>
+		<listitem><para><emphasis>(CI)</emphasis> Container Inherit  	0x2</para></listitem>
+		<listitem><para><emphasis>(NP)</emphasis> No Propagate Inherit	0x4</para></listitem>
+		<listitem><para><emphasis>(IO)</emphasis> Inherit Only       	0x8</para></listitem>
  	</itemizedlist>
   
-	<para>At present, flags can only be specified as decimal or
-	hexadecimal values.</para>
  
 	<para>The mask is a value which expresses the access right 
 	granted to the SID. It can be given as a decimal or hexadecimal value, 
@@ -271,6 +280,24 @@ ACL:<sid or name>:<type>/<flags>/<mask>
 	</refsect1>
 
 <refsect1>
+	<title>INHERITANCE</title>
+
+	<para>Per-ACE inheritance flags can be set in the ACE flags field. By
+	default, ACEs marked for object inheritance (OI) or container
+	inheritance (CI) are not propagated to sub-files or folders. However,
+	with the <parameter>--propagate-inheritance</parameter> arguement
+	specified, such ACEs are recursively applied to all applicable child
+	objects in the directory tree.</para>
+
+	<para>Any ACEs applied to sub-files of folders are marked with the
+	inherited (I) flag.</para>
+
+	<para>Files and folders with protected ACLs do not allow inheritable
+	permissions (set with <parameter>-I</parameter>). Such objects will
+	not receive ACEs flagged for inheritance with (CI) or (OI).</para>
+</refsect1>
+
+<refsect1>
 	<title>EXIT STATUS</title>
 
 	<para>The <command>smbcacls</command> program sets the exit status 
-- 
2.10.2


From 50d8a51d724be50ffdc0729d54a6b9a0a6eae061 Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Fri, 15 Nov 2013 11:53:35 +0000
Subject: [PATCH 4/4] doc: describe smbcacls --propagate-inheritance expanding
 INHERITANCE section

Signed-off-by: Noel Power <noel.power at suse.com>
---
 docs-xml/manpages/smbcacls.1.xml | 84 ++++++++++++++++++++++++++++++++++------
 1 file changed, 73 insertions(+), 11 deletions(-)

diff --git a/docs-xml/manpages/smbcacls.1.xml b/docs-xml/manpages/smbcacls.1.xml
index 00f4778..95dc5bc 100644
--- a/docs-xml/manpages/smbcacls.1.xml
+++ b/docs-xml/manpages/smbcacls.1.xml
@@ -27,7 +27,7 @@
 		<arg choice="opt">-S|--set acl</arg>
 		<arg choice="opt">-C|--chown name</arg>
 		<arg choice="opt">-G|--chgrp name</arg>
-		<arg choice="opt">-I allow|remove|copy</arg>
+		<arg choice="opt">-I|--inherit allow|remove|copy</arg>
 		<arg choice="opt">--propagate-inheritance</arg>
 		<arg choice="opt">--numeric</arg>
 		<arg choice="opt">-t</arg>
@@ -49,7 +49,7 @@
 	<manvolnum>7</manvolnum></citerefentry> suite.</para>
 
 	<para>The <command>smbcacls</command> program manipulates NT Access Control
-	Lists (ACLs) on SMB file shares. An ACL is comprised zero or more Access
+	Lists (ACLs) on SMB file shares. An ACL is comprised of zero or more Access
 	Control Entries (ACEs), which define access restrictions for a specific
 	user or group.</para>
 </refsect1>
@@ -283,18 +283,80 @@ ACL:<sid or name>:<type>/<flags>/<mask>
 	<title>INHERITANCE</title>
 
 	<para>Per-ACE inheritance flags can be set in the ACE flags field. By
-	default, ACEs marked for object inheritance (OI) or container
-	inheritance (CI) are not propagated to sub-files or folders. However,
-	with the <parameter>--propagate-inheritance</parameter> arguement
-	specified, such ACEs are recursively applied to all applicable child
-	objects in the directory tree.</para>
-
-	<para>Any ACEs applied to sub-files of folders are marked with the
-	inherited (I) flag.</para>
-
+	default, inheritable ACEs e.g. those marked for object inheritance (OI)
+	or container inheritance (CI), are not propagated to sub-files or
+	folders. However, with the
+	<parameter>--propagate-inheritance</parameter> argument specified, such
+	ACEs are automatically propagated according to some inheritance
+	rules.
+	<itemizedlist>
+		<listitem><para>Inheritable (OI)(CI) ACE flags can only be
+		applied to folders. </para></listitem>
+		<listitem><para>Any inheritable ACEs applied to sub-files or
+		folders are marked with the inherited (I) flag. Inheritable
+		ACE(s) are applied to folders unless the no propagation (NP)
+		flag is set. </para>
+		</listitem>
+		<listitem><para>When an ACE with the (OI) flag alone set is
+		progagated to a child folder the inheritance only flag (IO) is
+		also applied. This indicates the permissions associated with
+		the ACE don't apply to the folder itself (only to it's
+		child files). When applying the ACE to a child file the ACE is
+		inherited as normal.</para></listitem>
+		<listitem><para>When an ace with the (CI) flag alone set is
+		propagated to a child file there is no effect, when propagated
+		to a child folder it is inherited as normal.
+		</para></listitem>
+		<listitem><para>When an ACE that has both (OI) & (CI) flags
+		set the ACE is inherited as normal by both folders and
+		files.</para></listitem>
+	</itemizedlist></para>
+<para>(OI)(READ) added to parent folder</para>
+<para><programlisting>
++-parent/        (OI)(READ)
+| +-file.1       (I)(READ)
+| +-nested/      (OI)(IO)(I)(READ)
+  |   +-file.2   (I)(READ)
+</programlisting></para>
+<para>(CI)(READ) added to parent folder</para>
+<para><programlisting>
++-parent/        (CI)(READ)
+| +-file.1
+| +-nested/      (CI)(I)(READ)
+  |   +-file.2
+</programlisting></para>
+<para>(OI)(CI)(READ) added to parent folder</para>
+<para><programlisting>
++-parent/        (OI)(CI)(READ)
+| +-file.1       (I)(READ)
+| +-nested/      (OI)(CI)(I)(READ)
+  |   +-file.2   (I)(READ)
+</programlisting></para>
+<para>(OI)(NP)(READ) added to parent folder</para>
+<para><programlisting>
++-oi_dir/        (OI)(NP)(READ)
+| +-file.1       (I)(READ)
+| +-nested/
+|   +-file.2
+</programlisting></para>
+<para>(CI)(NP)(READ) added to parent folder</para>
+<para><programlisting>
++-oi_dir/        (CI)(NP)(READ)
+| +-file.1
+| +-nested/      (I)(READ)
+|   +-file.2
+</programlisting></para>
+<para>(OI)(CI)(NP)(READ) added to parent folder</para>
+<para><programlisting>
++-parent/        (CI)(OI)(NP)(READ)
+| +-file.1       (I)(READ)
+| +-nested/      (I)(READ)
+|   +-file.2
+</programlisting></para>
 	<para>Files and folders with protected ACLs do not allow inheritable
 	permissions (set with <parameter>-I</parameter>). Such objects will
 	not receive ACEs flagged for inheritance with (CI) or (OI).</para>
+
 </refsect1>
 
 <refsect1>
-- 
2.10.2



More information about the samba-technical mailing list