1#!/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 2009 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 2023 RackTop Systems, Inc.
31#
32
33. $STF_SUITE/tests/functional/acl/acl_common.kshlib
34
35#
36# DESCRIPTION:
37#	Verify that the write_owner for
38#	owner/group/everyone are correct.
39#
40# STRATEGY:
41# 1. Create file and  directory in zfs filesystem
42# 2. Set special write_owner ACE to the file and directory
43# 3. Try to chown/chgrp of the file and directory to take owner/group
44# 4. Verify that the owner/group are correct. Follow these rules:
45#	(1) If uid is granted the write_owner permission, then it can only do
46#	    chown to its own uid, or a group that they are a member of.
47#	(2) chown/chgrp will fail when write_owner not granted.
48#	(3) Superuser will always permit whatever they do.
49#
50
51verify_runnable "both"
52
53function cleanup
54{
55	[[ -d $basedir ]] && rm -rf $basedir
56	[[ -f $TESTDIR/$ARCHIVEFILE ]] && log_must rm -f $TESTDIR/$ARCHIVEFILE
57	return 0
58}
59
60log_assert "Verify that the chown/chgrp could take owner/group " \
61	"while permission is granted."
62log_onexit cleanup
63
64#
65# Get the owner of a file/directory
66#
67function get_owner
68{
69	typeset node=$1
70
71	if [[ -z $node ]]; then
72		log_fail "node are not defined."
73	fi
74
75	echo $(ls -dl $node | awk '{print $3}')
76}
77
78#
79# Get the group of a file/directory
80#
81function get_group
82{
83	typeset node=$1
84
85	if [[ -z $node ]]; then
86		log_fail "node are not defined."
87	fi
88
89	echo $(ls -dl $node | awk '{print $4}')
90}
91
92
93#
94# Get the group name that a UID belongs to
95#
96function get_user_group
97{
98	typeset uid=$1
99	typeset value
100
101	if [[ -z $uid ]]; then
102		log_fail "UID not defined."
103	fi
104
105	value=$(id $uid)
106
107	if [[ $? -eq 0 ]]; then
108		value=${value##*\(}
109		value=${value%%\)*}
110		echo $value
111	else
112		log_fail "Invalid UID (uid)."
113	fi
114}
115
116function operate_node_owner
117{
118	typeset user=$1
119	typeset node=$2
120	typeset old_owner=$3
121	typeset expect_owner=$4
122	typeset ret new_owner
123
124	if [[ $user == "" || $node == "" ]]; then
125		log_fail "user, node are not defined."
126	fi
127
128	su $user -c "chown $expect_owner $node"
129	ret=$?
130	new_owner=$(get_owner $node)
131
132	if [[ $new_owner != $old_owner ]]; then
133		tar xpf $TESTDIR/$ARCHIVEFILE
134	fi
135
136	if [[ $ret -eq 0 ]]; then
137		if [[ $new_owner != $expect_owner ]]; then
138			log_note "Owner not changed as expected " \
139				"($old_owner|$new_owner|$expect_owner), " \
140				"but return code is $ret."
141			return 1
142		fi
143	elif [[ $ret -ne 0 && $new_owner != $old_owner ]]; then
144		log_note "Owner changed ($old_owner|$new_owner), " \
145			"but return code is $ret."
146		return 2
147	fi
148
149	return $ret
150}
151
152function operate_node_group
153{
154	typeset user=$1
155	typeset node=$2
156	typeset old_group=$3
157	typeset expect_group=$4
158	typeset ret new_group
159
160	if [[ $user == "" || $node == "" ]]; then
161		log_fail "user, node are not defined."
162	fi
163
164	su $user -c "chgrp $expect_group $node"
165	ret=$?
166	new_group=$(get_group $node)
167
168	if [[ $new_group != $old_group ]]; then
169		tar xpf $TESTDIR/$ARCHIVEFILE
170	fi
171
172	if [[ $ret -eq 0 ]]; then
173		if [[ $new_group != $expect_group ]]; then
174			log_note "Group not changed as expected " \
175				"($old_group|$new_group|$expect_group), " \
176				"but return code is $ret."
177			return 1
178		fi
179	elif [[ $ret -ne 0 && $new_group != $old_group ]]; then
180		log_note "Group changed ($old_group|$new_group), " \
181			"but return code is $ret."
182		return 2
183	fi
184
185	return $ret
186}
187
188function logname
189{
190	typeset acl_target=$1
191	typeset user=$2
192	typeset old=$3
193	typeset new=$4
194	typeset ret="log_mustnot"
195
196	# To super user, read and write deny permission was override.
197	if [[ $user == root ]]; then
198		ret="log_must"
199	elif [[ $user == $new ]] ; then
200		if [[ $user == $old || $acl_target == *:allow ]]; then
201			# with aclimplicit=on, the write_owner:deny
202			# will have no effect and chown/chgrp commands
203			# needs to succeed.
204			if [[ $aclimplicit == on ||
205			    $acl_target != *@:write_owner:deny ]]; then
206				ret="log_must"
207			fi
208		fi
209	fi
210
211	print $ret
212}
213
214function check_chmod_results
215{
216	typeset user=$1
217	typeset node=$2
218	typeset flag=$3
219	typeset acl_target=$3:$4
220	typeset g_usr=$5
221	typeset o_usr=$6
222	typeset log old_owner old_group new_owner new_group
223
224	old_owner=$(get_owner $node)
225	old_group=$(get_group $node)
226
227	if [[ $flag == "owner@" || $flag == "everyone@" ]]; then
228		for new_owner in $user "nobody"; do
229			new_group=$(get_user_group $new_owner)
230
231			log=$(logname $acl_target $user \
232				$old_owner $new_owner)
233
234			$log operate_node_owner $user $node \
235				$old_owner $new_owner
236
237			$log operate_node_group $user $node \
238				$old_group $new_group
239		done
240	fi
241	if [[ $flag == "group@" || $flag == "everyone@" ]]; then
242		for new_owner in $g_usr "nobody"; do
243			new_group=$(get_user_group $new_owner)
244
245			log=$(logname $acl_target $g_usr $old_owner \
246				$new_owner)
247
248			$log operate_node_owner $g_usr $node \
249				$old_owner $new_owner
250
251			$log operate_node_group $g_usr \
252				$node $old_group $new_group
253		done
254	fi
255	if [[ $flag == "everyone@" ]]; then
256		for new_owner in $g_usr "nobody"; do
257			new_group=$(get_user_group $new_owner)
258
259			log=$(logname $acl_target $o_usr $old_owner \
260				$new_owner)
261
262			$log operate_node_owner $o_usr $node \
263				$old_owner $new_owner
264
265			$log operate_node_group $o_usr $node \
266				$old_group $new_group
267		done
268	fi
269}
270
271function test_chmod_basic_access
272{
273	typeset user=$1
274	typeset node=${2%/}
275	typeset g_usr=$3
276	typeset o_usr=$4
277	typeset flag acl_t
278
279	for flag in $a_flag; do
280		for acl_t in $a_access; do
281			log_must su $user -c "chmod A+$flag:$acl_t $node"
282
283			tar cpf $TESTDIR/$ARCHIVEFILE basedir
284
285			check_chmod_results $user $node $flag $acl_t $g_usr \
286			    $o_usr
287
288			log_must su $user -c "chmod A0- $node"
289		done
290	done
291}
292
293function setup_test_files
294{
295	typeset base_node=$1
296	typeset user=$2
297	typeset group=$3
298
299	rm -rf $base_node
300
301	log_must mkdir -p $base_node
302	log_must chown $user:$group $base_node
303
304	# Prepare all files/sub-dirs for testing.
305	log_must su $user -c "touch $file"
306	log_must su $user -c "chmod 444 $file"
307	log_must su $user -c "mkdir -p $dir"
308	log_must su $user -c "chmod 444 $dir"
309	log_must su $user -c "chmod 555 $base_node"
310}
311
312typeset ARCHIVEFILE=archive.tar
313typeset a_prop="on off"
314typeset a_access="write_owner:allow write_owner:deny"
315typeset a_flag="owner@ group@ everyone@"
316typeset basedir="$TESTDIR/basedir"
317typeset file="$basedir/file"
318typeset dir="$basedir/dir"
319typeset aclimplicit=$(zfs get -Ho value aclimplicit $TESTPOOL/$TESTFS)
320typeset val
321
322cd $TESTDIR
323
324for val in $a_prop; do
325	log_must zfs set aclimplicit=$val $TESTPOOL/$TESTFS
326	aclimplicit=$(zfs get -Ho value aclimplicit $TESTPOOL/$TESTFS)
327	if [[ $val == off ]]; then
328		# aclimplicit=off also needs aclmode=passthrough and
329		# aclinherit=passthrough
330		log_must zfs set aclmode=passthrough $TESTPOOL/$TESTFS
331		log_must zfs set aclinherit=passthrough $TESTPOOL/$TESTFS
332	fi
333
334	setup_test_files $basedir 'root' 'root'
335	test_chmod_basic_access 'root' $file $ZFS_ACL_ADMIN  $ZFS_ACL_OTHER1
336	test_chmod_basic_access 'root' $dir $ZFS_ACL_ADMIN  $ZFS_ACL_OTHER1
337	rm -rf $basedir
338
339	setup_test_files $basedir $ZFS_ACL_STAFF1 $ZFS_ACL_STAFF_GROUP
340	test_chmod_basic_access $ZFS_ACL_STAFF1 $file $ZFS_ACL_STAFF2 \
341		$ZFS_ACL_OTHER1
342	test_chmod_basic_access $ZFS_ACL_STAFF1 $dir $ZFS_ACL_STAFF2 \
343		$ZFS_ACL_OTHER1
344	rm -rf $basedir
345done
346
347# restore defaults, so next test is not affected.
348log_must zfs inherit aclmode $TESTPOOL/$TESTFS
349log_must zfs inherit aclinherit $TESTPOOL/$TESTFS
350log_must zfs inherit aclimplicit $TESTPOOL/$TESTFS
351
352log_pass "Verify that the chown/chgrp could take owner/group " \
353    "while permission is granted."
354