#!/bin/sh
#
#

uidrange="60000:100000"
gidrange="60000:100000"
uidinrange="nobody"
uidoutrange="daemon"
gidinrange="nobody" # We expect $uidinrange in this group
gidoutrange="daemon" # We expect $uidinrange in this group


check_ko()
{
	if [ $(sysctl -n security.mac.bsdextended.enabled) = "0" ]; then
		# The kernel module is loaded but disabled.  Enable it for the
		# duration of the test.
		touch enabled_bsdextended
		sysctl security.mac.bsdextended.enabled=1
	fi
}

setup()
{
	check_ko
	mkdir mnt
	[ -c /dev/mdctl ] || atf_skip "no /dev/mdctl to create md devices"
	mdmfs -s 25m md mnt \
		|| atf_fail "failed to mount md device"
	chmod a+rwx mnt
	md_device=$(mount -p | grep "$PWD/mnt" | awk '{ gsub(/^\/dev\//, "", $1); print $1 }')
	if [ -z "$md_device" ]; then
		atf_fail "md device not properly attached to the system"
	fi
	echo $md_device > md_device

	ugidfw remove 1

	cat > mnt/test-script.sh <<'EOF'
#!/bin/sh
: > $1
EOF
	if [ $? -ne 0 ]; then
		atf_fail "failed to create test script"
	fi

	file1=mnt/test-$uidinrange
	file2=mnt/test-$uidoutrange
	command1="sh mnt/test-script.sh $file1"
	command2="sh mnt/test-script.sh $file2"

	# $uidinrange file
	atf_check -s exit:0 su -m $uidinrange -c "$command1"

	chown "$uidinrange":"$gidinrange" $file1
	chmod a+w $file1

	# $uidoutrange file
	if ! $command2; then
		atf_fail $desc
	fi

	chown "$uidoutrange":"$gidoutrange" $file2
	chmod a+w $file2
}

cleanup()
{
	ugidfw remove 1

	umount -f mnt
	if [ -f md_device ]; then
		mdconfig -d -u $( cat md_device )
	fi
	if [ -f enabled_bsdextended ]; then
		sysctl security.mac.bsdextended.enabled=0
	fi
}

atf_test_case no_rules cleanup
no_rules_head()
{
	atf_set "require.user" "root"
}
no_rules_body()
{
	setup

	# no rules $uidinrange
	atf_check -s exit:0 su -fm $uidinrange -c "$command1"

	# no rules $uidoutrange
	atf_check -s exit:0 su -fm $uidoutrange -c "$command1"
}
no_rules_cleanup()
{
	cleanup
}

atf_test_case subject_match_on_uid cleanup
subject_match_on_uid_head()
{
	atf_set "require.user" "root"
}
subject_match_on_uid_body()
{
	setup

	atf_check -s exit:0 ugidfw set 1 subject uid $uidrange object mode rasx
	# subject uid in range
	atf_check -s not-exit:0 -e match:"Permission denied" \
		su -fm $uidinrange -c "$command1"

	# subject uid out range
	atf_check -s exit:0 su -fm $uidoutrange -c "$command1"

}
subject_match_on_uid_cleanup()
{
	cleanup
}

atf_test_case subject_match_on_gid cleanup
subject_match_on_gid_head()
{
	atf_set "require.user" "root"
}
subject_match_on_gid_body()
{
	setup

	atf_check -s exit:0 ugidfw set 1 subject gid $gidrange object mode rasx

	# subject gid in range
	atf_check -s not-exit:0 -e match:"Permission denied" \
		su -fm $uidinrange -c "$command1"

	# subject gid out range
	atf_check -s exit:0 su -fm $uidoutrange -c "$command1"
}
subject_match_on_gid_cleanup()
{
	cleanup
}

atf_test_case subject_match_on_jail cleanup
subject_match_on_jail_head()
{
	atf_set "require.progs" "jail"
	atf_set "require.user" "root"
}
subject_match_on_jail_body()
{
	setup

	atf_expect_fail "this testcase fails (see bug # 205481)"
	# subject matching jailid
	jailid=`jail -i / localhost 127.0.0.1 /usr/sbin/daemon -f /bin/sh -c "(sleep 5; touch mnt/test-jail) &"`
	atf_check -s exit:0 ugidfw set 1 subject jailid $jailid object mode rasx
	sleep 10

	if [ -f mnt/test-jail ]; then
		atf_fail "$desc"
	fi

	rm -f mnt/test-jail
	# subject nonmatching jailid
	jailid=`jail -i / localhost 127.0.0.1 /usr/sbin/daemon -f /bin/sh -c "(sleep 5; touch mnt/test-jail) &"`
	sleep 10
	if ! [ -f mnt/test-jail ]; then
		atf_fail $desc
	fi
}
subject_match_on_jail_cleanup()
{
	cleanup
}

atf_test_case object_uid cleanup
object_uid_head()
{
	atf_set "require.user" "root"
}
object_uid_body()
{
	setup

	atf_check -s exit:0 ugidfw set 1 subject object uid $uidrange mode rasx

	# object uid in range
	atf_check -s not-exit:0 -e match:"Permission denied" \
		su -fm $uidinrange -c "$command1"

	# object uid out range
	atf_check -s exit:0 su -fm $uidinrange -c "$command2"
	atf_check -s exit:0 ugidfw set 1 subject object uid $uidrange mode rasx

	# object uid in range (different subject)
	atf_check -s not-exit:0 -e match:"Permission denied" \
		su -fm $uidoutrange -c "$command1"

	# object uid out range (different subject)
	atf_check -s exit:0 su -fm $uidoutrange -c "$command2"

}
object_uid_cleanup()
{
	cleanup
}

atf_test_case object_gid cleanup
object_gid_head()
{
	atf_set "require.user" "root"
}
object_gid_body()
{
	setup

	atf_check -s exit:0 ugidfw set 1 subject object gid $uidrange mode rasx

	# object gid in range
	atf_check -s not-exit:0 -e match:"Permission denied" \
		su -fm $uidinrange -c "$command1"

	# object gid out range
	atf_check -s exit:0 su -fm $uidinrange -c "$command2"
	# object gid in range (different subject)
	atf_check -s not-exit:0 -e match:"Permission denied" \
		su -fm $uidoutrange -c "$command1"

	# object gid out range (different subject)
	atf_check -s exit:0 su -fm $uidoutrange -c "$command2"
}
object_gid_cleanup()
{
	cleanup
}

atf_test_case object_filesys cleanup
object_filesys_head()
{
	atf_set "require.user" "root"
}
object_filesys_body()
{
	setup

	atf_check -s exit:0 ugidfw set 1 subject uid $uidrange object filesys / mode rasx
	# object out of filesys
	atf_check -s exit:0 su -fm $uidinrange -c "$command1"

	atf_check -s exit:0 ugidfw set 1 subject uid $uidrange object filesys mnt mode rasx
	# object in filesys
	atf_check -s not-exit:0 -e match:"Permission denied" \
		su -fm $uidinrange -c "$command1"
}
object_filesys_cleanup()
{
	cleanup
}

atf_test_case object_suid cleanup
object_suid_head()
{
	atf_set "require.user" "root"
}
object_suid_body()
{
	setup

	atf_check -s exit:0 ugidfw set 1 subject uid $uidrange object suid mode rasx
	# object notsuid
	atf_check -s exit:0 su -fm $uidinrange -c "$command1"

	chmod u+s $file1
	# object suid
	atf_check -s not-exit:0 -e match:"Permission denied" \
		su -fm $uidinrange -c "$command1"
	chmod u-s $file1

}
object_suid_cleanup()
{
	cleanup
}

atf_test_case object_sgid cleanup
object_sgid_head()
{
	atf_set "require.user" "root"
}
object_sgid_body()
{
	setup

	atf_check -s exit:0 ugidfw set 1 subject uid $uidrange object sgid mode rasx
	# object notsgid
	atf_check -s exit:0 su -fm $uidinrange -c "$command1"

	chmod g+s $file1
	# object sgid
	atf_check -s not-exit:0 -e match:"Permission denied" \
		su -fm $uidinrange -c "$command1"
	chmod g-s $file1
}
object_sgid_cleanup()
{
	cleanup
}

atf_test_case object_uid_matches_subject cleanup
object_uid_matches_subject_head()
{
	atf_set "require.user" "root"
}
object_uid_matches_subject_body()
{
	setup

	atf_check -s exit:0 ugidfw set 1 subject uid $uidrange object uid_of_subject mode rasx

	# object uid notmatches subject
	atf_check -s exit:0 su -fm $uidinrange -c "$command2"

	# object uid matches subject
	atf_check -s not-exit:0 -e match:"Permission denied" \
		su -fm $uidinrange -c "$command1"
}
object_uid_matches_subject_cleanup()
{
	cleanup
}

atf_test_case object_gid_matches_subject cleanup
object_gid_matches_subject_head()
{
	atf_set "require.user" "root"
}
object_gid_matches_subject_body()
{
	setup

	atf_check -s exit:0 ugidfw set 1 subject uid $uidrange object gid_of_subject mode rasx

	# object gid notmatches subject
	atf_check -s exit:0 su -fm $uidinrange -c "$command2"

	# object gid matches subject
	atf_check -s not-exit:0 -e match:"Permission denied" \
		su -fm $uidinrange -c "$command1"

}
object_gid_matches_subject_cleanup()
{
	cleanup
}

atf_test_case object_type cleanup
object_type_head()
{
	atf_set "require.user" "root"
}
object_type_body()
{
	setup

	# object not type
	atf_check -s exit:0 ugidfw set 1 subject uid $uidrange object type dbclsp mode rasx
	atf_check -s exit:0 su -fm $uidinrange -c "$command1"

	# object type
	atf_check -s exit:0 ugidfw set 1 subject uid $uidrange object type r mode rasx
	atf_check -s not-exit:0 -e match:"Permission denied" \
		su -fm $uidinrange -c "$command1"
}
object_type_cleanup()
{
	cleanup
}

atf_init_test_cases()
{
	atf_add_test_case no_rules
	atf_add_test_case subject_match_on_uid
	atf_add_test_case subject_match_on_gid
	atf_add_test_case subject_match_on_jail
	atf_add_test_case object_uid
	atf_add_test_case object_gid
	atf_add_test_case object_filesys
	atf_add_test_case object_suid
	atf_add_test_case object_sgid
	atf_add_test_case object_uid_matches_subject
	atf_add_test_case object_gid_matches_subject
	atf_add_test_case object_type
}
