#! /usr/libexec/atf-sh
# $FreeBSD$

PIDFILE=ggatessh.pid
TESTIMG="test.img"
TEMPFILE="random.data"
SFTPSERVER="/usr/libexec/sftp-server"
PORT=2222

atf_test_case ggatessh cleanup
ggatessh_head()
{
	atf_set "descr" "ggatessh can proxy to sftp"
	atf_set "require.progs" "ggatessh"
	atf_set "require.user" "root"
	atf_set "timeout" 10
}

ggatessh_body()
{
	load_ggate
	us=$(alloc_ggate_dev)

	n1mchunks=3
	secsize=4096

	atf_check -e ignore -o ignore \
	    dd if=/dev/random of="$TEMPFILE" bs=1m count=$n1mchunks conv=notrunc

	startup_sshd

	truncate -s ${n1mchunks}m "$TESTIMG"
	# sshd authenticates and switches to USER
	chown "$USER" "$TESTIMG"

	echo 'WARNING: ggatessh error messages goes to syslog' \
	     '(aka /var/log/messages)'

	atf_check \
	    ggatessh create -i "$(pwd)/id_rsa" -p "$PORT" -F "$PIDFILE" \
	        -u $us -l "$USER" 127.0.0.1 "$(pwd)/$TESTIMG"

	ggate_dev=/dev/ggate${us}

	wait_for_ggate_device ${ggate_dev}

	# make sure it has correct size and sector sizekj
	read _dev _secsize _size _nsecs _stripesize _stripeoff <<EOF
$(diskinfo /dev/ggate$us)
EOF
	atf_check_equal "$_secsize" $secsize
	atf_check_equal "$_size" $(($n1mchunks * 1024 * 1024))
	atf_check_equal "$_nsecs" $(($n1mchunks * 1024 * 1024 / $secsize))

	# Test writing
	atf_check -e ignore -o ignore \
	    dd if="$TEMPFILE" of=${ggate_dev} bs=1m count=$n1mchunks \
	        conv=notrunc

	# Test reading
	atf_check -e ignore -o ignore \
	    dd of="$TEMPFILE"2 if=${ggate_dev} bs=1m count=$n1mchunks \
	        conv=notrunc

	# Verify that we read what we wrote
	atf_check cmp "$TEMPFILE" "$TEMPFILE"2

	# Verify that the image matches
	atf_check cmp "$TEMPFILE" "$TESTIMG"

	rm "$TEMPFILE" "$TEMPFILE"2
}

ggatessh_cleanup()
{

	common_cleanup
}

atf_test_case ggatessh_resize cleanup
ggatessh_resize_head()
{
	atf_set "descr" "ggatessh will resize the devices"
	atf_set "require.progs" "ggatessh"
	atf_set "require.user" "root"
	atf_set "timeout" 20
}

ggatessh_resize_body()
{

	n1mchunks=4
	secsize=4096
	us=$(alloc_ggate_dev)

	startup_sshd

	truncate -s ${n1mchunks}m "$TESTIMG"
	# sshd authenticates and switches to USER
	chown "$USER" "$TESTIMG"

	echo 'WARNING: ggatessh error messages goes to syslog' \
	    '(aka /var/log/messages)'

	atf_check \
	    ggatessh create -i "$(pwd)/id_rsa" -p "$PORT" -F "$PIDFILE" \
	        -u $us -l "$USER" 127.0.0.1 "$(pwd)/$TESTIMG"

	ggate_dev=/dev/ggate${us}

	wait_for_ggate_device ${ggate_dev}

	# make sure it has correct size and sector sizekj
	read _dev _secsize _size _nsecs _stripesize _stripeoff <<EOF
$(diskinfo /dev/ggate$us)
EOF
	atf_check_equal "$_secsize" $secsize
	atf_check_equal "$_size" $(($n1mchunks * 1024 * 1024))
	atf_check_equal "$_nsecs" $(($n1mchunks * 1024 * 1024 / $secsize))

	# kill off old ggate
	pkill -F "$PIDFILE"

	# Test resizing
	n1mchunks=6
	truncate -s ${n1mchunks}m "$TESTIMG"

	ps auxwww | grep ggatessh
	cat "$PIDFILE"

	sleep 1

	# restart ggate
	atf_check \
	    ggatessh rescue -v -i "$(pwd)/id_rsa" -p "$PORT" -F "$PIDFILE" \
	        -u $us -l "$USER" 127.0.0.1 "$(pwd)/$TESTIMG" &

	sleep 1

	# make sure it has correct size and sector size
	read _dev _secsize _size _nsecs _stripesize _stripeoff <<EOF
$(diskinfo /dev/ggate$us)
EOF
	atf_check_equal "$_secsize" $secsize
	atf_check_equal "$_size" $(($n1mchunks * 1024 * 1024))
	atf_check_equal "$_nsecs" $(($n1mchunks * 1024 * 1024 / $secsize))

	dd if=/dev/ggate$us of=/dev/null bs=1m
}

ggatessh_resize_cleanup()
{

	common_cleanup
}

atf_test_case ggatessh_rowotest cleanup
ggatessh_rowotest_head()
{
	atf_set "descr" "ggatessh properly handles the -o flag"
	atf_set "require.progs" "ggatessh"
	atf_set "require.user" "root"
	atf_set "timeout" 10
}

ggatessh_rowotest_body()
{

	n1mchunks=4
	secsize=4096
	us=$(alloc_ggate_dev)

	startup_sshd

	truncate -s ${n1mchunks}m "$TESTIMG"
	# sshd authenticates and switches to USER
	chmod 444 "$TESTIMG"

	echo 'WARNING: ggatessh error messages goes to syslog' \
	    '(aka /var/log/messages)'

	# make sure it fails in rw mode
	atf_check -s not-exit:0 \
	    ggatessh create -i "$(pwd)/id_rsa" -p "$PORT" -F "$PIDFILE" \
	        -u $us -l "$USER" 127.0.0.1 "$(pwd)/$TESTIMG"

	# open it in read-only mode
	atf_check \
	    ggatessh create -o ro -i "$(pwd)/id_rsa" -p "$PORT" -F "$PIDFILE" \
	        -u $us -l "$USER" 127.0.0.1 "$(pwd)/$TESTIMG"

	ggate_dev=/dev/ggate${us}

	wait_for_ggate_device ${ggate_dev}

	# make sure it has correct size and sector sizekj
	read _dev _secsize _size _nsecs _stripesize _stripeoff <<EOF
$(diskinfo /dev/ggate$us)
EOF
	atf_check_equal "$_secsize" $secsize
	atf_check_equal "$_size" $(($n1mchunks * 1024 * 1024))
	atf_check_equal "$_nsecs" $(($n1mchunks * 1024 * 1024 / $secsize))

	# that we can read a read-only ggate device
	atf_check -e ignore \
	    dd if=${ggate_dev} of=/dev/null bs=1m count=$n1mchunks

	# that we can not write a read-only ggate device
	atf_check -e ignore -s not-exit:0 \
	    dd of=${ggate_dev} if=/dev/zero bs=1m count=1

	# kill off old ggate
	pkill -F "$PIDFILE"
	ggatessh destroy -f -u $us >/dev/null

	# test write-only
	chmod 222 "$TESTIMG"

	# make sure it fails in rw mode
	atf_check -s not-exit:0 \
	    ggatessh create -i "$(pwd)/id_rsa" -p "$PORT" -F "$PIDFILE" \
	        -u $us -l "$USER" 127.0.0.1 "$(pwd)/$TESTIMG"

	# open it in write-only mode
	atf_check \
	    ggatessh create -o wo -i "$(pwd)/id_rsa" -p "$PORT" \
	        -F "$PIDFILE" -u $us -l "$USER" 127.0.0.1 "$(pwd)/$TESTIMG"

	ggate_dev=/dev/ggate${us}

	wait_for_ggate_device ${ggate_dev}

	# Note: diskinfo opens w/ read, can't verify device info

	# that we can not read a write-only ggate device
	atf_check -e ignore -s not-exit:0 \
	    dd if=${ggate_dev} of=/dev/null bs=1m count=$n1mchunks

	# that we can write a write-only ggate device
	atf_check -e ignore \
	    dd of=${ggate_dev} if=/dev/zero bs=1m count=1
}

ggatessh_rowotest_cleanup()
{

	common_cleanup
}

atf_init_test_cases()
{
	atf_add_test_case ggatessh
	atf_add_test_case ggatessh_resize
	atf_add_test_case ggatessh_rowotest
}

alloc_ggate_dev()
{
	local us

	us=0
	while [ -c /dev/ggate${us} ]; do
		: $(( us += 1 ))
	done
	echo ${us} > ggate.devs
	echo ${us}
}

alloc_md()
{
	local md

	md=$(mdconfig -a -t malloc -s 1M) || \
		atf_fail "failed to allocate md device"
	echo ${md} >> md.devs
	echo ${md}
}

# slightly modified from:
# https://serverfault.com/questions/344295/is-it-possible-to-run-sshd-as-a-normal-user
startup_sshd()
{
	# ===============================================================
	# Note: using shorter keys to speed up tests, these are insecure.
	# ===============================================================

	# Host keys
	ssh-keygen -f ssh_host_rsa_key -b 1024 -N '' -t rsa > /dev/null

	# user key
	ssh-keygen -f id_rsa -b 1024 -N '' -t rsa > /dev/null

	(echo -n 'command="/usr/libexec/sftp-server" '; cat id_rsa.pub) > authorized_keys

	cat > sshd_config <<EOF
ListenAddress 127.0.0.1:$PORT
HostKey /home/freebsd/custom_ssh/ssh_host_rsa_key
AuthorizedKeysFile  $(pwd)/authorized_keys
ChallengeResponseAuthentication no
PasswordAuthentication no
# to allow writable tmp w/ sticky bit
StrictModes no
UsePAM no
Subsystem   sftp    ${SFTPSERVER}
PidFile $(pwd)/sshd.pid
EOF

	if ! :; then
		/usr/sbin/sshd -dD -f $(pwd)/sshd_config &
		sleep .2
	else
		/usr/sbin/sshd -f $(pwd)/sshd_config
		while ! [ -f sshd.pid ]; do
			sleep .2
		done
	fi
}

checksum()
{
	local src work
	src=$1
	work=$2

	src_checksum=$(md5 -q $src)
	work_checksum=$(md5 -q $work)

	if [ "$work_checksum" != "$src_checksum" ]; then
		atf_fail "work md5 checksum didn't match"
	fi

	ggate_checksum=$(md5 -q /dev/ggate${us})
	if [ "$ggate_checksum" != "$src_checksum" ]; then
		atf_fail "ggate md5 checksum didn't match"
	fi
}

common_cleanup()
{
	if [ -f "ggate.devs" ]; then
		while read test_ggate; do
			ggatessh destroy -f -u $test_ggate >/dev/null
		done < ggate.devs
		rm ggate.devs
	fi

	if [ -f "sshd.pid" ]; then
		pkill -F sshd.pid
		# clean up after startup_sshd
		rm ssh_host_rsa_key
		rm id_rsa id_rsa.pub
		rm authorized_keys
	fi

	if [ -f "$PIDFILE" ]; then
		pkill -F "$PIDFILE"
		rm $PIDFILE
	fi

	if [ -f "PLAINFILES" ]; then
		while read f; do
			rm -f ${f}
		done < ${PLAINFILES}
		rm ${PLAINFILES}
	fi

	if [ -f "md.devs" ]; then
		while read test_md; do
			mdconfig -d -u $test_md 2>/dev/null
		done < md.devs
		rm md.devs
	fi
	true
}

load_ggate()
{
	local class=gate

	# If the geom class isn't already loaded, try loading it.
	if ! kldstat -q -m g_${class}; then
		if ! geom ${class} load; then
			atf_skip "could not load module for geom class=${class}"
		fi
	fi
}

# Bug 204616: ggatel(8) creates /dev/ggate* asynchronously if `ggatel create`
#             isn't called with `-v`.
wait_for_ggate_device()
{
	ggate_device=$1

	while [ ! -c $ggate_device ]; do
		sleep 0.5
	done
}
