1#!/usr/bin/ksh -p
2#
3# CDDL HEADER START
4#
5# The contents of this file are subject to the terms of the
6# Common Development and Distribution License (the "License").
7# You may not use this file except in compliance with the License.
8#
9# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10# or http://www.opensolaris.org/os/licensing.
11# See the License for the specific language governing permissions
12# and limitations under the License.
13#
14# When distributing Covered Code, include this CDDL HEADER in each
15# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16# If applicable, add the following below this CDDL HEADER, with the
17# fields enclosed by brackets "[]" replaced with your own identifying
18# information: Portions Copyright [yyyy] [name of copyright owner]
19#
20# CDDL HEADER END
21#
22
23#
24# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
25# Use is subject to license terms.
26#
27
28#
29# Copyright (c) 2012, 2016 by Delphix. All rights reserved.
30# Copyright 2016 Nexenta Systems, Inc.
31#
32
33
34. $STF_SUITE/tests/functional/acl/acl_common.kshlib
35
36# DESCRIPTION:
37# Verify chmod have correct behaviour on directories and files when
38# filesystem has the different aclmode setting
39#
40# STRATEGY:
41# 1. Loop super user and non-super user to run the test case.
42# 2. Create basedir and a set of subdirectores and files within it.
43# 3. Separately chmod basedir with different aclmode options,
44#    combine with the variable setting of aclmode:
45#    "discard", "groupmask", or "passthrough".
46# 4. Verify each directories and files have the correct access control
47#    capability.
48
49verify_runnable "both"
50
51function cleanup
52{
53	(( ${#cwd} != 0 )) && cd $cwd
54
55	[[ -f $TARFILE ]] && log_must rm -f $TARFILE
56	[[ -d $basedir ]] && log_must rm -rf $basedir
57}
58
59log_assert "Verify chmod have correct behaviour to directory and file when" \
60    "filesystem has the different aclmode setting"
61log_onexit cleanup
62
63set -A aclmode_flag "discard" "groupmask" "passthrough"
64
65set -A ace_prefix \
66    "user:$ZFS_ACL_OTHER1" \
67    "user:$ZFS_ACL_OTHER2" \
68    "group:$ZFS_ACL_STAFF_GROUP" \
69    "group:$ZFS_ACL_OTHER_GROUP"
70
71set -A argv "000" "444" "644" "777" "755" "231" "562" "413"
72
73set -A ace_file_preset \
74    "read_data" \
75    "write_data" \
76    "append_data" \
77    "execute" \
78    "read_data/write_data" \
79    "read_data/write_data/append_data" \
80    "write_data/append_data" \
81    "read_data/execute" \
82    "write_data/append_data/execute" \
83    "read_data/write_data/append_data/execute"
84
85# Define the base directory and file
86basedir=$TESTDIR/basedir;  ofile=$basedir/ofile; odir=$basedir/odir
87nfile=$basedir/nfile; ndir=$basedir/ndir
88
89TARFILE=$TESTDIR/tarfile
90
91# Verify all the node have expected correct access control
92allnodes="$nfile $ndir"
93
94# According to the original bits, the input ACE access and ACE type, return the
95# expect bits after 'chmod A0{+|=}'.
96#
97# $1 isdir indicate if the target is a directory
98# $2 bits which was make up of three bit 'rwx'
99# $3 bits_limit which was make up of three bit 'rwx'
100# $4 ACE access which is read_data, write_data or execute
101# $5 ctrl which is to determine allow or deny according to owner/group bit
102function cal_bits # isdir bits bits_limit acl_access ctrl
103{
104	typeset -i isdir=$1
105	typeset -i bits=$2
106	typeset -i bits_limit=$3
107	typeset acl_access=$4
108	typeset -i ctrl=${5:-0}
109	typeset flagr=0 flagw=0 flagx=0
110	typeset tmpstr
111
112	if (( ctrl == 0 )); then
113		if (( (( bits & 4 )) != 0 )); then
114			flagr=1
115		fi
116		if (( (( bits & 2 )) != 0 )); then
117			flagw=1
118		fi
119		if (( (( bits & 1 )) != 0 )); then
120			flagx=1
121		fi
122	else
123		# Determine ACE as per owner/group bit
124		flagr=1
125		flagw=1
126		flagx=1
127
128		if (( ((bits & 4)) != 0 )) && \
129			(( ((bits_limit & 4)) != 0 )); then
130			flagr=0
131		fi
132		if (( ((bits & 2)) != 0 )) && \
133			(( ((bits_limit & 2)) != 0 )); then
134			flagw=0
135		fi
136		if (( ((bits & 1)) != 0 )) && \
137			(( ((bits_limit & 1)) != 0 )); then
138			flagx=0
139		fi
140	fi
141
142	if ((flagr != 0)); then
143		if [[ $acl_access == *"read_data"* ]]; then
144			if ((isdir != 0)); then
145				tmpstr=${tmpstr}/list_directory
146			fi
147			tmpstr=${tmpstr}/read_data
148		fi
149	fi
150
151	if ((flagw != 0)); then
152		if [[ $acl_access == *"write_data"* ]]; then
153			if ((isdir != 0)); then
154				tmpstr=${tmpstr}/add_file
155			fi
156			tmpstr=${tmpstr}/write_data
157		fi
158		if [[ $acl_access == *"append_data"* ]]; then
159			if ((isdir != 0)); then
160				tmpstr=${tmpstr}/add_subdirectory
161			fi
162			tmpstr=${tmpstr}/append_data
163		fi
164	fi
165
166	if ((flagx != 0)); then
167		if [[ $acl_access == *"execute"* ]]; then
168			tmpstr=${tmpstr}/execute
169		fi
170	fi
171
172	tmpstr=${tmpstr#/}
173
174	echo "$tmpstr"
175}
176
177#
178# To translate an ace if the node is dir
179#
180# $1 isdir indicate if the target is a directory
181# $2 acl to be translated
182#
183function translate_acl # isdir acl
184{
185	typeset -i isdir=$1
186	typeset acl=$2
187	typeset who prefix acltemp action
188
189	if ((isdir != 0)); then
190		who=${acl%%:*}
191		prefix=$who
192		acltemp=${acl#*:}
193		acltemp=${acltemp%%:*}
194		prefix=$prefix:$acltemp
195		action=${acl##*:}
196		acl=$prefix:$(cal_bits $isdir 7 7 $acl 0):$action
197	fi
198	echo "$acl"
199}
200
201#
202# To verify if a new ACL is generated as result of
203# chmod operation.
204#
205# $1 bit indicates whether owner/group bit
206# $2 newmode indicates the mode changed using chmod
207# $3 isdir indicate if the target is a directory
208#
209function check_new_acl # bit newmode isdir
210{
211	typeset bits=$1
212	typeset mode=$2
213	typeset -i isdir=$3
214	typeset new_acl
215	typeset gbit
216	typeset ebit
217	typeset str=":"
218	typeset dc=""
219
220	gbit=${mode:1:1}
221	ebit=${mode:2:1}
222	if (( ((bits & 4)) == 0 )); then
223		if (( ((gbit & 4)) != 0 || \
224		    ((ebit & 4)) != 0 )); then
225			if ((isdir == 0)); then
226				new_acl=${new_acl}${str}read_data
227			else
228				new_acl=${new_acl}${str}list_directory/read_data
229			fi
230			str="/"
231		fi
232	fi
233	if (( ((bits & 2)) == 0 )); then
234		if (( ((gbit & 2)) != 0 || \
235		    ((ebit & 2)) != 0 )); then
236			if ((isdir == 0)); then
237				new_acl=${new_acl}${str}write_data/append_data
238			else
239				new_acl=${new_acl}${str}add_file/write_data/
240				new_acl=${new_acl}add_subdirectory/append_data
241				dc="/delete_child"
242			fi
243			str="/"
244		fi
245	fi
246	if (( ((bits & 1)) == 0 )); then
247		if (( ((gbit & 1)) != 0 || \
248		    ((ebit & 1)) != 0 )); then
249				new_acl=${new_acl}${str}execute
250		fi
251	fi
252	new_acl=${new_acl}${dc}
253	echo "$new_acl"
254}
255
256function build_new_acl # newmode isdir
257{
258	typeset newmode=$1
259	typeset isdir=$2
260	typeset expect
261	if ((flag == 0)); then
262		prefix="owner@"
263		bit=${newmode:0:1}
264		status=$(check_new_acl $bit $newmode $isdir)
265
266	else
267		prefix="group@"
268		bit=${newmode:1:1}
269		status=$(check_new_acl $bit $newmode $isdir)
270	fi
271	expect=$prefix$status:deny
272	echo $expect
273}
274
275# According to inherited flag, verify subdirectories and files within it has
276# correct inherited access control.
277function verify_aclmode # <aclmode> <node> <newmode>
278{
279	# Define the nodes which will be affected by inherit.
280	typeset aclmode=$1
281	typeset node=$2
282	typeset newmode=$3
283
284	# count: the ACE item to fetch
285	# passcnt: counter, if it achieves to maxnumber,
286	#	then no additional ACE should apply.
287
288	typeset -i count=0 passcnt=0
289	typeset -i bits=0 obits=0 bits_owner=0 isdir=0
290	typeset -i total_acl
291	typeset -i acl_count=$(count_ACE $node)
292
293	((total_acl = maxnumber + 3))
294
295	if [[ -d $node ]]; then
296		((isdir = 1))
297	fi
298
299	((i = maxnumber - 1))
300	count=0
301	passcnt=0
302	flag=0
303	while ((i >= 0)); do
304		expect1=${acls[$i]}
305		passthrough=0
306		#
307		# aclmode=passthrough,
308		# no changes will be made to the ACL other than
309		# generating the necessary ACL entries to represent
310		# the new mode of the file or directory.
311		#
312		# aclmode=discard,
313		# delete all ACL entries that don't represent
314		# the mode of the file.
315		#
316		# aclmode=groupmask,
317		# reduce user or group permissions.  The permissions are
318		# reduced, such that they are no greater than the group
319		# permission bits, unless it is a user entry that has the
320		# same UID as the owner of the file or directory.
321		# Then, the ACL permissions are reduced so that they are
322		# no greater than owner permission bits.
323		#
324
325		case $aclmode in
326		passthrough)
327			if ((acl_count > total_acl)); then
328				expect1=$(build_new_acl $newmode $isdir)
329				flag=1
330				((total_acl = total_acl + 1))
331				((i = i + 1))
332			else
333				passthrough=1
334				expect1=$(translate_acl $isdir $expect1)
335			fi
336			;;
337		groupmask)
338			if ((acl_count > total_acl)); then
339				expect1=$(build_new_acl $newmode $isdir)
340				flag=1
341				((total_acl = total_acl + 1))
342				((i = i + 1))
343			elif [[ $expect1 == *":allow"* ]]; then
344				who=${expect1%%:*}
345				aclaction=${expect1##*:}
346				prefix=$who
347				acltemp=""
348				reduce=0
349				# To determine the mask bits
350				# according to the entry type.
351				#
352				case $who in
353				owner@)
354					pos=0
355					;;
356				group@)
357					pos=1
358					;;
359				everyone@)
360					pos=2
361					;;
362				user)
363					acltemp=${expect1#*:}
364					acltemp=${acltemp%%:*}
365					owner=$(get_owner $node)
366					group=$(get_group $node)
367					if [[ $acltemp == $owner ]]; then
368						pos=0
369					else
370						pos=1
371					fi
372					prefix=$prefix:$acltemp
373					;;
374				group)
375					acltemp=${expect1#*:}
376					acltemp=${acltemp%%:*}
377					pos=1
378					prefix=$prefix:$acltemp
379					reduce=1
380					;;
381				esac
382
383				obits=${newmode:$pos:1}
384				((bits = $obits))
385				# permission should be no greater than the
386				# group permission bits
387				if ((reduce != 0)); then
388					((bits &= ${newmode:1:1}))
389					# The ACL permissions are reduced so
390					# that they are no greater than owner
391					# permission bits.
392					((bits_owner = ${newmode:0:1}))
393					((bits &= $bits_owner))
394				fi
395
396				if ((bits < obits)) && [[ -n $acltemp ]]; then
397					expect2=$prefix:
398					new_bit=$(cal_bits $isdir $obits \
399					    $bits_owner $expect1 0)
400					expect2=${expect2}${new_bit}:allow
401				else
402					expect2=$prefix:
403					new_bit=$(cal_bits $isdir $obits \
404					    $obits $expect1 0)
405					expect2=${expect2}${new_bit}:allow
406				fi
407
408				priv=$(cal_bits $isdir $obits $bits_owner \
409				    $expect2 0)
410				expect1=$prefix:$priv:$aclaction
411			else
412				expect1=$(translate_acl $isdir $expect1)
413			fi
414			;;
415		discard)
416			passcnt=maxnumber
417			break
418			;;
419		esac
420
421		# Get the first ACE to do comparison
422		aclcur=$(get_ACE $node $count)
423		aclcur=${aclcur#$count:}
424		if [[ -n $expect1 && $expect1 != $aclcur ]]; then
425			ls -vd $node
426			log_fail "$aclmode $i #$count " \
427				"ACE: $aclcur, expect to be " \
428				"$expect1"
429		fi
430		((count = count + 1))
431		((i = i - 1))
432	done
433
434	#
435	# If there's no any ACE be checked, it should be identify as
436	# an normal file/dir, verify it.
437	#
438	if ((passcnt == maxnumber)); then
439		if [[ -d $node ]]; then
440			compare_acls $node $odir
441		elif [[	-f $node ]]; then
442			compare_acls $node $ofile
443		fi
444
445		if [[ $? -ne 0 ]]; then
446			ls -vd $node
447			log_fail "Unexpect acl: $node, $aclmode ($newmode)"
448		fi
449	fi
450}
451
452
453
454typeset -i maxnumber=0
455typeset acl
456typeset target
457typeset -i passthrough=0
458typeset -i flag=0
459
460cd $TESTDIR
461for mode in "${aclmode_flag[@]}"; do
462	log_must zfs set aclmode=$mode $TESTPOOL/$TESTFS
463
464	for user in root $ZFS_ACL_STAFF1; do
465		log_must set_cur_usr $user
466
467		log_must usr_exec mkdir $basedir
468
469		log_must usr_exec mkdir $odir
470		log_must usr_exec touch $ofile
471		log_must usr_exec mkdir $ndir
472		log_must usr_exec touch $nfile
473
474		for obj in $allnodes; do
475			maxnumber=0
476			for preset in "${ace_file_preset[@]}"; do
477				for prefix in "${ace_prefix[@]}"; do
478					acl=$prefix:$preset
479
480					case $((maxnumber % 2)) in
481					0)
482						acl=$acl:deny
483						;;
484					1)
485						acl=$acl:allow
486						;;
487					esac
488
489					log_must usr_exec chmod A+$acl $obj
490					acls[$maxnumber]=$acl
491
492					((maxnumber = maxnumber + 1))
493				done
494			done
495			# Archive the file and directory
496			log_must tar cpf@ $TARFILE ${basedir#$TESTDIR/}
497
498			if [[ -d $obj ]]; then
499				target=$odir
500			elif [[ -f $obj ]]; then
501				target=$ofile
502			fi
503			for newmode in "${argv[@]}"; do
504				log_must usr_exec chmod $newmode $obj
505				log_must usr_exec chmod $newmode $target
506				log_must verify_aclmode $mode $obj $newmode
507				log_must tar xpf@ $TARFILE
508			done
509		done
510
511		log_must usr_exec rm -rf $basedir $TARFILE
512	done
513done
514
515log_pass "Verify chmod behaviour co-op with aclmode setting passed"
516