#! /bin/sh
# Automated libuser fs function regression tester
#
# Copyright (c) 2012 Red Hat, Inc. All rights reserved.
#
# This is free software; you can redistribute it and/or modify it under
# the terms of the GNU Library General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
#
# Author: Miloslav Trmač <mitr@redhat.com>

# Some tests runs only as root. If you want to run those tests,
# you must run it as a real root and set the variable USE_FAKEROOT=no
# Make sure you have backups!
if [ -z "$USE_FAKEROOT" ] ; then
    USE_FAKEROOT=yes
fi

export USE_FAKEROOT

if [ "$USE_FAKEROOT" != "no" ]; then
    if ! fakeroot --version &>/dev/null; then
        echo 'fakeroot not available, skipping test' >&2
        exit 77
    fi
fi

run_test() {
    if [ "$USE_FAKEROOT" = "no" ]; then
        ( "$@"; )
    else
        fakeroot "$@"
    fi
}

srcdir=$srcdir/tests

workdir=$(pwd)/test_fs
export workdir

umask 002

trap 'status=$?; chmod -R u+rwx "$workdir"; rm -rf "$workdir"; exit $status' 0
trap '(exit 1); exit 1' 1 2 13 15

rm -rf "$workdir"
mkdir "$workdir"

# Set up an the environment
mkdir "$workdir"/files
> "$workdir"/files/passwd
> "$workdir"/files/shadow
> "$workdir"/files/group
> "$workdir"/files/gshadow

# Set up the client
LIBUSER_CONF=$workdir/libuser.conf
export LIBUSER_CONF
sed "s|@WORKDIR@|$workdir|g; s|@TOP_BUILDDIR@|$(pwd)|g" \
    < "$srcdir"/fs.conf.in > "$LIBUSER_CONF"
# Ugly non-portable hacks
LD_LIBRARY_PATH=$(pwd)/lib/.libs
export LD_LIBRARY_PATH
PYTHONPATH=$(pwd)/python/.libs
export PYTHONPATH

# Like ls, but filtered to only contain interesting columns and avoid
# varying data.  Expects one argument, a directory path
filtered_ls() {
    (
	cd "$1";
	LC_ALL=C ls -lnR | \
	    awk 'NF > 3 { printf("%.10s %4d %4d %s\n", $1, $3, $4, $9); }
             NF <= 3 && !/total/ { print }';
    )
}
export -f filtered_ls


# Test lu_homedir_remove()
test_lu_homedir_remove() {
    # User's "own" content
    mkdir -p "$workdir"/rm/root/{dir,unreadable}
    touch "$workdir"/rm/{kept,root/{dir,unreadable}/f}
    mkfifo "$workdir"/rm/root/fifo 2>/dev/null
    ln -s ../kept "$workdir"/rm/root/symlink
    chown -R 555:555 "$workdir"/rm/root
    chmod 701 "$workdir"/rm/root
    chmod 000 "$workdir"/rm/root/unreadable

    # Other content in the home directory
    mknod "$workdir"/rm/root/block b 1 1
    mknod "$workdir"/rm/root/char c 1 1
    echo 'foo' > "$workdir"/rm/secret
    chmod 000 "$workdir"/rm/secret
    ln "$workdir"/rm/{secret,root/hardlink}

    $VALGRIND $PYTHON "$srcdir"/fs_test.py --remove "$workdir/rm/root"
    if [ $? -ne 0 ]; then
	exit 1
    fi

    filtered_ls "$workdir/rm"
}
export -f test_lu_homedir_remove
run_test test_lu_homedir_remove > "$workdir"/rm_output

diff "$workdir"/rm_output - <<EOF
.:
-rw-rw-r--    0    0 kept
----------    0    0 secret
EOF
if [ $? -ne 0 ]; then
    echo "Failed: test_lu_homedir_remove" >&1
    exit 1
fi


# Test lu_homedir_remove_for_user_if_owned()
test_lu_homedir_remove_for_user_if_owned1() {
    # User's "own" content
    mkdir -p "$workdir"/rm_owned1/root/dir
    touch "$workdir"/rm_owned1/root/dir/f
    chown -R 555:555 "$workdir"/rm_owned1/root
    chmod 701 "$workdir"/rm_owned1/root

    # Other content in the home directory
    mkdir "$workdir"/rm_owned1/root/non-owned-dir
    touch "$workdir"/rm_owned1/root/non-owned-dir/f

    $VALGRIND $PYTHON "$srcdir"/fs_test.py --remove-if-owned \
	"$workdir/rm_owned1/root" 555
    if [ $? -ne 0 ]; then
	exit 1
    fi

    filtered_ls "$workdir"/rm_owned1
}
export -f test_lu_homedir_remove_for_user_if_owned1
run_test test_lu_homedir_remove_for_user_if_owned1 > "$workdir"/rm_owned_output1

diff "$workdir"/rm_owned_output1 - <<EOF
.:
EOF
if [ $? -ne 0 ]; then
    echo "Failed: test_lu_homedir_remove_for_user_if_owned1" >&1
    exit 1
fi

# Nothing is touched if the home directory is not owned by the right UID
test_lu_homedir_remove_for_user_if_owned2() {
    # Non-owned home directory
    mkdir -p "$workdir"/rm_owned2/root/dir
    touch "$workdir"/rm_owned2/root/dir/f
    chown -R 555:555 "$workdir"/rm_owned2/root
    chown 444:444 "$workdir"/rm_owned2/root
    chmod 701 "$workdir"/rm_owned2/root

    # Other content in the home directory
    mkdir "$workdir"/rm_owned2/root/non-owned-dir
    touch "$workdir"/rm_owned2/root/non-owned-dir/f

    $VALGRIND $PYTHON "$srcdir"/fs_test.py --remove-if-owned \
	"$workdir/rm_owned2/root" 555
    echo $?

    filtered_ls "$workdir"/rm_owned2
}
export -f test_lu_homedir_remove_for_user_if_owned2
run_test test_lu_homedir_remove_for_user_if_owned2 \
	> "$workdir"/rm_owned_output2 2>&1

diff "$workdir"/rm_owned_output2 - <<EOF
\`$workdir/rm_owned2/root' is not owned by UID \`555'
1
.:
drwx-----x  444  444 root

./root:
drwxrwxr-x  555  555 dir
drwxrwxr-x    0    0 non-owned-dir

./root/dir:
-rw-rw-r--  555  555 f

./root/non-owned-dir:
-rw-rw-r--    0    0 f
EOF
if [ $? -ne 0 ]; then
    echo "Failed: test_lu_homedir_remove_for_user_if_owned2" >&1
    exit 1
fi


# Prepare an "interesting" directory, to be used both directly as a home
# directory and as a skeleton.  Note: changes the current directory!
create_source_directory() {
    mkdir -p "$1"/{dir,unreadable,group-owned}
    # User's "own" content
    for i in "$1" "$1"/dir "$1"/group-owned; do
	touch "$i"/f "$i"/setuid
	mkfifo "$i"/fifo 2>/dev/null
	ln -s ../outside "$i"/symlink
    done
    mkdir "$1"/setgid
    chmod g+s "$1"/setgid
    echo 'content' > "$1"/unreadable/f
    chmod 000 "$1"/unreadable/f "$1"/unreadable
    chown -R 555:555 "$1"
    chgrp -R 444 "$1"/group-owned
    for i in "$1" "$1"/dir "$1"/group-owned; do
	chmod u+xs "$i"/setuid
    done

    # Other content in the home directory
    mknod "$1"/block b 1 1
    mknod "$1"/char c 1 1
    echo 'foo' > "$1"/secret
    chmod 000 "$1"/secret
}
export -f create_source_directory


# Test lu_homedir_move()
test_lu_homedir_move1() {
    create_source_directory "$workdir"/home1

    $VALGRIND $PYTHON "$srcdir"/fs_test.py --move "$workdir"/{home1,home2}
    if [ $? -ne 0 ]; then
	exit 1
    fi

    filtered_ls "$workdir"/home2
}

if [ $USE_FAKEROOT = "no" ] ; then
    export -f test_lu_homedir_move1
    run_test test_lu_homedir_move1 > "$workdir"/mv_output

    # Special files and fifos are not copied over.  Ownership and permissions are
    # preserved.
    diff "$workdir"/mv_output - <<EOF
.:
drwxrwxr-x  555  555 dir
-rw-rw-r--  555  555 f
drwxrwxr-x  555  444 group-owned
----------    0    0 secret
drwxrwsr-x  555  555 setgid
-rwsrw-r--  555  555 setuid
lrwxrwxrwx  555  555 symlink
d---------  555  555 unreadable

./dir:
-rw-rw-r--  555  555 f
-rwsrw-r--  555  555 setuid
lrwxrwxrwx  555  555 symlink

./group-owned:
-rw-rw-r--  555  444 f
-rwsrw-r--  555  444 setuid
lrwxrwxrwx  555  444 symlink

./setgid:

./unreadable:
----------  555  555 f
EOF
    if [ $? -ne 0 ]; then
        echo "Failed: test_lu_homedir_move1" >&2
        exit 1
    fi
else
    echo "Skipped: test_lu_homedir_move1" >&2
fi

# Moving onto an existing directory is prohibited
test_lu_homedir_move2() {
    mkdir "$workdir"/mv2home{1,2}

    $VALGRIND $PYTHON "$srcdir"/fs_test.py --move "$workdir"/mv2home{1,2}
    echo $?
}

if [ $USE_FAKEROOT = "no" ] ; then
    export -f test_lu_homedir_move2
    run_test test_lu_homedir_move2 > "$workdir"/mv2_output 2>&1
    diff "$workdir"/mv2_output - <<EOF
Error creating \`$workdir/mv2home2': File exists
1
EOF
    if [ $? -ne 0 ]; then
        echo "Failed: test_lu_homedir_move2" >&2
        exit 1
    fi
else
    echo "Skipped: test_lu_homedir_move2" >&2
fi

# Test lu_homedir_populate()
function test_lu_homedir_populate1() {
    create_source_directory "$workdir"/skel
    chown -R 0:0 "$workdir"/skel
    chgrp -R 444 "$workdir"/skel/group-owned
    # chown/chgrp have reset permissions, fix them up
    for i in "$workdir"/skel "$workdir"/skel/dir "$workdir"/skel/group-owned; do
	chmod u+xs "$i"/setuid
    done

    $VALGRIND $PYTHON "$srcdir"/fs_test.py --populate "$workdir"/newhome 556 557
    if [ $? -ne 0 ]; then
	exit 1
    fi

    filtered_ls "$workdir"/newhome
}

if [ $USE_FAKEROOT = "no" ] ; then
    export -f test_lu_homedir_populate1
    run_test test_lu_homedir_populate1 > "$workdir"/pop_output

    # Special files and fifos are not copied over.  Permissions are preserved,
    # ownership is changed to the desired values except for non-root groups
    diff "$workdir"/pop_output - <<EOF
.:
drwxrwxr-x  556  557 dir
-rw-rw-r--  556  557 f
drwxrwxr-x  556  444 group-owned
----------  556  557 secret
drwxrwsr-x  556  557 setgid
-rwsrw-r--  556  557 setuid
lrwxrwxrwx  556  557 symlink
d---------  556  557 unreadable

./dir:
-rw-rw-r--  556  557 f
-rwsrw-r--  556  557 setuid
lrwxrwxrwx  556  557 symlink

./group-owned:
-rw-rw-r--  556  444 f
-rwsrw-r--  556  444 setuid
lrwxrwxrwx  556  444 symlink

./setgid:

./unreadable:
----------  556  557 f
EOF
    if [ $? -ne 0 ]; then
        echo "Failed: test_lu_homedir_populate1" >&2
        exit 1
    fi
else
    echo "Skipped: test_lu_homedir_populate1" >&2
fi

# Populating an existing directory is prohibited
function test_lu_homedir_populate2() {
    rm -rf "$workdir"/skel
    mkdir "$workdir"/{skel,pop2}

    $VALGRIND $PYTHON "$srcdir"/fs_test.py --populate "$workdir"/pop2 556 557
    echo $?
}
export -f test_lu_homedir_populate2
run_test test_lu_homedir_populate2 > "$workdir"/pop2_output 2>&1
diff "$workdir"/pop2_output - <<EOF
Error creating \`$workdir/pop2': File exists
1
EOF
if [ $? -ne 0 ]; then
    echo "Failed: test_lu_homedir_populate2" >&2
    exit 1
fi
