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 [[ $acl_access == *"allow"* &&
145			    $passthrough == 0 ]]; then
146				tmpstr=${tmpstr}
147			elif ((isdir == 0)); then
148				tmpstr=${tmpstr}/read_data
149			else
150				tmpstr=${tmpstr}/list_directory/read_data
151			fi
152		fi
153	fi
154
155	if ((flagw != 0)); then
156		if [[ $acl_access == *"allow"* && $passthrough == 0 ]]; then
157			tmpstr=${tmpstr}
158		else
159			if [[ $acl_access == *"write_data"* ]]; then
160				if ((isdir == 0)); then
161					tmpstr=${tmpstr}/write_data
162				else
163					tmpstr=${tmpstr}/add_file/write_data
164				fi
165			fi
166			if [[ $acl_access == *"append_data"* ]]; then
167				if ((isdir == 0)); then
168					tmpstr=${tmpstr}/append_data
169				else
170					tmpstr=${tmpstr}/add_subdirectory
171					tmpstr=${tmpstr}/append_data
172				fi
173			fi
174		fi
175	fi
176
177	if ((flagx != 0)); then
178		if [[ $acl_access == *"execute"* ]]; then
179			if [[ $acl_access == *"allow"* &&
180			    $passthrough == 0 ]]; then
181				tmpstr=${tmpstr}
182			else
183				tmpstr=${tmpstr}/execute
184			fi
185		fi
186	fi
187
188	tmpstr=${tmpstr#/}
189
190	echo "$tmpstr"
191}
192
193#
194# To translate an ace if the node is dir
195#
196# $1 isdir indicate if the target is a directory
197# $2 acl to be translated
198#
199function translate_acl # isdir acl
200{
201	typeset -i isdir=$1
202	typeset acl=$2
203	typeset who prefix acltemp action
204
205	if ((isdir != 0)); then
206		who=${acl%%:*}
207		prefix=$who
208		acltemp=${acl#*:}
209		acltemp=${acltemp%%:*}
210		prefix=$prefix:$acltemp
211		action=${acl##*:}
212		acl=$prefix:$(cal_bits $isdir 7 7 $acl 0):$action
213	fi
214	echo "$acl"
215}
216
217#
218# To verify if a new ACL is generated as result of
219# chmod operation.
220#
221# $1 bit indicates whether owner/group bit
222# $2 newmode indicates the mode changed using chmod
223# $3 isdir indicate if the target is a directory
224#
225function check_new_acl # bit newmode isdir
226{
227	typeset bits=$1
228	typeset mode=$2
229	typeset -i isdir=$3
230	typeset new_acl
231	typeset gbit
232	typeset ebit
233	typeset str=":"
234	typeset dc=""
235
236	gbit=${mode:1:1}
237	ebit=${mode:2:1}
238	if (( ((bits & 4)) == 0 )); then
239		if (( ((gbit & 4)) != 0 || \
240		    ((ebit & 4)) != 0 )); then
241			if ((isdir == 0)); then
242				new_acl=${new_acl}${str}read_data
243			else
244				new_acl=${new_acl}${str}list_directory/read_data
245			fi
246			str="/"
247		fi
248	fi
249	if (( ((bits & 2)) == 0 )); then
250		if (( ((gbit & 2)) != 0 || \
251		    ((ebit & 2)) != 0 )); then
252			if ((isdir == 0)); then
253				new_acl=${new_acl}${str}write_data/append_data
254			else
255				new_acl=${new_acl}${str}add_file/write_data/
256				new_acl=${new_acl}add_subdirectory/append_data
257				dc="/delete_child"
258			fi
259			str="/"
260		fi
261	fi
262	if (( ((bits & 1)) == 0 )); then
263		if (( ((gbit & 1)) != 0 || \
264		    ((ebit & 1)) != 0 )); then
265				new_acl=${new_acl}${str}execute
266		fi
267	fi
268	new_acl=${new_acl}${dc}
269	echo "$new_acl"
270}
271
272function build_new_acl # newmode isdir
273{
274	typeset newmode=$1
275	typeset isdir=$2
276	typeset expect
277	if ((flag == 0)); then
278		prefix="owner@"
279		bit=${newmode:0:1}
280		status=$(check_new_acl $bit $newmode $isdir)
281
282	else
283		prefix="group@"
284		bit=${newmode:1:1}
285		status=$(check_new_acl $bit $newmode $isdir)
286	fi
287	expect=$prefix$status:deny
288	echo $expect
289}
290
291# According to inherited flag, verify subdirectories and files within it has
292# correct inherited access control.
293function verify_aclmode # <aclmode> <node> <newmode>
294{
295	# Define the nodes which will be affected by inherit.
296	typeset aclmode=$1
297	typeset node=$2
298	typeset newmode=$3
299
300	# count: the ACE item to fetch
301	# pass: to mark if the current ACE should apply to the target
302	# passcnt: counter, if it achieves to maxnumber,
303	#	then no additional ACE should apply.
304
305	typeset -i count=0 pass=0 passcnt=0
306	typeset -i bits=0 obits=0 bits_owner=0 isdir=0
307	typeset -i total_acl
308	typeset -i acl_count=$(count_ACE $node)
309
310	((total_acl = maxnumber + 3))
311
312	if [[ -d $node ]]; then
313		((isdir = 1))
314	fi
315
316	((i = maxnumber - 1))
317	count=0
318	passcnt=0
319	flag=0
320	while ((i >= 0)); do
321		pass=0
322		expect1=${acls[$i]}
323		passthrough=0
324		#
325		# aclmode=passthrough,
326		# no changes will be made to the ACL other than
327		# generating the necessary ACL entries to represent
328		# the new mode of the file or directory.
329		#
330		# aclmode=discard,
331		# delete all ACL entries that don't represent
332		# the mode of the file.
333		#
334		# aclmode=groupmask,
335		# reduce user or group permissions.  The permissions are
336		# reduced, such that they are no greater than the group
337		# permission bits, unless it is a user entry that has the
338		# same UID as the owner of the file or directory.
339		# Then, the ACL permissions are reduced so that they are
340		# no greater than owner permission bits.
341		#
342
343		case $aclmode in
344		passthrough)
345			if ((acl_count > total_acl)); then
346				expect1=$(build_new_acl $newmode $isdir)
347				flag=1
348				((total_acl = total_acl + 1))
349				((i = i + 1))
350			else
351				passthrough=1
352				expect1=$(translate_acl $isdir $expect1)
353			fi
354			;;
355		groupmask)
356			if ((acl_count > total_acl)); then
357				expect1=$(build_new_acl $newmode $isdir)
358				flag=1
359				((total_acl = total_acl + 1))
360				((i = i + 1))
361			elif [[ $expect1 == *":allow"* ]]; then
362				who=${expect1%%:*}
363				aclaction=${expect1##*:}
364				prefix=$who
365				acltemp=""
366				reduce=0
367				# To determine the mask bits
368				# according to the entry type.
369				#
370				case $who in
371				owner@)
372					pos=0
373					;;
374				group@)
375					pos=1
376					;;
377				everyone@)
378					pos=2
379					;;
380				user)
381					acltemp=${expect1#*:}
382					acltemp=${acltemp%%:*}
383					owner=$(get_owner $node)
384					group=$(get_group $node)
385					if [[ $acltemp == $owner ]]; then
386						pos=0
387					else
388						pos=1
389					fi
390					prefix=$prefix:$acltemp
391					;;
392				group)
393					acltemp=${expect1#*:}
394					acltemp=${acltemp%%:*}
395					pos=1
396					prefix=$prefix:$acltemp
397					reduce=1
398					;;
399				esac
400
401				obits=${newmode:$pos:1}
402				((bits = $obits))
403				# permission should be no greater than the
404				# group permission bits
405				if ((reduce != 0)); then
406					((bits &= ${newmode:1:1}))
407					# The ACL permissions are reduced so
408					# that they are no greater than owner
409					# permission bits.
410					((bits_owner = ${newmode:0:1}))
411					((bits &= $bits_owner))
412				fi
413
414				if ((bits < obits)) && [[ -n $acltemp ]]; then
415					expect2=$prefix:
416					new_bit=$(cal_bits $isdir $obits \
417					    $bits_owner $expect1 1)
418					expect2=${expect2}${new_bit}:allow
419				else
420					expect2=$prefix:
421					new_bit=$(cal_bits $isdir $obits \
422					    $obits $expect1 1)
423					expect2=${expect2}${new_bit}:allow
424				fi
425
426				priv=$(cal_bits $isdir $obits $bits_owner \
427				    $expect2 0)
428				expect1=$prefix:$priv:$aclaction
429			else
430				expect1=$(translate_acl $isdir $expect1)
431			fi
432			;;
433		discard)
434			passcnt=maxnumber
435			break
436			;;
437		esac
438
439		if ((pass == 0)) ; then
440			# Get the first ACE to do comparison
441			aclcur=$(get_ACE $node $count)
442			aclcur=${aclcur#$count:}
443			if [[ -n $expect1 && $expect1 != $aclcur ]]; then
444				ls -vd $node
445				log_fail "$aclmode $i #$count " \
446					"ACE: $aclcur, expect to be " \
447					"$expect1"
448			fi
449		((count = count + 1))
450		fi
451		((i = i - 1))
452	done
453
454	#
455	# If there's no any ACE be checked, it should be identify as
456	# an normal file/dir, verify it.
457	#
458	if ((passcnt == maxnumber)); then
459		if [[ -d $node ]]; then
460			compare_acls $node $odir
461		elif [[	-f $node ]]; then
462			compare_acls $node $ofile
463		fi
464
465		if [[ $? -ne 0 ]]; then
466			ls -vd $node
467			log_fail "Unexpect acl: $node, $aclmode ($newmode)"
468		fi
469	fi
470}
471
472
473
474typeset -i maxnumber=0
475typeset acl
476typeset target
477typeset -i passthrough=0
478typeset -i flag=0
479
480for mode in "${aclmode_flag[@]}"; do
481	log_must zfs set aclmode=$mode $TESTPOOL/$TESTFS
482
483	for user in root $ZFS_ACL_STAFF1; do
484		log_must set_cur_usr $user
485
486		log_must usr_exec mkdir $basedir
487
488		log_must usr_exec mkdir $odir
489		log_must usr_exec touch $ofile
490		log_must usr_exec mkdir $ndir
491		log_must usr_exec touch $nfile
492
493		for obj in $allnodes; do
494			maxnumber=0
495			for preset in "${ace_file_preset[@]}"; do
496				for prefix in "${ace_prefix[@]}"; do
497					acl=$prefix:$preset
498
499					case $((maxnumber % 2)) in
500					0)
501						acl=$acl:deny
502						;;
503					1)
504						acl=$acl:allow
505						;;
506					esac
507
508					log_must usr_exec chmod A+$acl $obj
509					acls[$maxnumber]=$acl
510
511					((maxnumber = maxnumber + 1))
512				done
513			done
514			# Archive the file and directory
515			log_must tar cpf@ $TARFILE $basedir
516
517			if [[ -d $obj ]]; then
518				target=$odir
519			elif [[ -f $obj ]]; then
520				target=$ofile
521			fi
522			for newmode in "${argv[@]}"; do
523				log_must usr_exec chmod $newmode $obj
524				log_must usr_exec chmod $newmode $target
525				log_must verify_aclmode $mode $obj $newmode
526				log_must tar xpf@ $TARFILE
527			done
528		done
529
530		log_must usr_exec rm -rf $basedir $TARFILE
531	done
532done
533
534log_pass "Verify chmod behaviour co-op with aclmode setting passed"
535