#!/bin/bash
# testing script for cryptmount (compiled with -DTESTING)
# $Revision: 130 $, $Date: 2006-10-15 11:42:36 +0100 (Sun, 15 Oct 2006) $
# RW Penney, December 2005

DD=/bin/dd
LOSETUP=/sbin/losetup
TMPDIR=/tmp/cm-$$
CM=./cryptmount
PASSWD="hopeless"
USER1=bin
USER2=nobody
LOOPDEV=/dev/loop7
LOOPDEV2=/dev/loop5


#
# testing infrastructure
#

NTESTS_RUN=0
NTESTS_FAILED=0
NTESTS_PASSED=0
NTESTS_ABORTED=0

function test_start() {
    # syntax: test_start <test-name>
    echo -n "testing $1..."
    echo -e "\n\n----  test \"$1\" ----\n" 1>&3
    if [ ${NTESTS_ABORTED} -gt 0 ]; then
        test_abort
        false
        return
    else
        NTESTS_RUN=`expr ${NTESTS_RUN} + 1`
        true
        return
    fi
};

function test_fail() {
    echo "  FAILED!   [$1]"
    echo "!!! TEST FAILED [$1] !!!" 1>&3
    NTESTS_FAILED=`expr ${NTESTS_FAILED} + 1`
};

function test_pass() {
    echo "  passed"
    echo "(test passed)" 1>&3
    NTESTS_PASSED=`expr ${NTESTS_PASSED} + 1`
};

function test_abort() {
    echo "  aborted"
    echo "(test aborted)" 1>&3
    NTESTS_ABORTED=`expr ${NTESTS_ABORTED} + 1`
};

function test_summary() {
    echo "========"
    echo "${NTESTS_RUN} tests run"
    echo "  ${NTESTS_FAILED} tests failed"
    echo "  ${NTESTS_PASSED} tests passed"
    echo -e "\n\n${NTESTS_RUN}/${NTESTS_FAILED}/${NTESTS_PASSED} tests run/failed/passed" 1>&3
};



#
# utility routines
#

function mkkeyfile() {
    # syntax: mkkeyfile <bytes> <message_digest> <cipher>
    ${DD} if=/dev/urandom bs=${1}c count=1 2>/dev/null | \
    openssl enc -e -pass pass:${PASSWD} -md $2 -${3}
};

function mkrandshort() {
    # create random digit
    od -An -N2 -t x2 /dev/urandom | sed 's% *%%g'
};

function mkbingrep() {
    # create simple binary-grep for block-offset test
    cat <<EOF > "${1}.c"
#include <unistd.h>
#include <stdio.h>
#define BLKLEN 32

int main(int argc, char*argv[])
{   int i,notzeros,state=0;
    long fpos=0;
    char buff[BLKLEN];

    while (read(STDIN_FILENO,(void*)buff,BLKLEN) == BLKLEN && state < 2) {
        for (notzeros=0,i=0; !notzeros && i<BLKLEN; ++i) {
            notzeros |= buff[i];
        }
        if (state == 0 && notzeros) {
            printf("%ld ", fpos);
            ++state;
        } else if (state == 1 && !notzeros) {
            printf("%ld\n", fpos);
            ++state;
        }
        fpos += BLKLEN;
    }
    return 0;
}
EOF
    gcc -O "${1}.c" -o "${1}" && rm "${1}.c"
};


#
# specific test-cases
#

function test_version() {
    # check that cryptmount has been compiled properly for further tests
    if test_start "version"; then true; else return; fi
    echo "#nothing here!" > ${TMPDIR}/cmtab
    if ${CM} --config-dir ${TMPDIR} --version 2>&3; then
        test_pass
    else
        test_abort
        echo "*** please ensure cryptmount has been compiled with -DTESTING"
        echo "*** or rebuild using 'make clean cmtest'"
    fi
};


function test_binary() {
    # run built-in unit-tests
    if test_start "binary self-test"; then true; else return; fi
    if ${CM} --self-test 2>&3; then
        test_pass
    else
        test_abort
    fi
};


function test_setup_dev() {
    # basic test of prepare/release on raw device
    if test_start "basic setup (device)"; then true; else return; fi
    mkkeyfile 16 md5 aes-128-cbc > ${TMPDIR}/keyfile
    idx=`mkrandshort`
    cat <<EOF > ${TMPDIR}/cmtab
    target${idx} {
        dev=${LOOPDEV}
        dir=${TMPDIR}/mnt
        fstype=ext2 fsoptions=defaults cipher=twofish
        keyfile=${TMPDIR}/keyfile keyhash=md5 keycipher=aes-128-cbc
    }
EOF
    if ${CM} --config-dir ${TMPDIR} --password ${PASSWD} --prepare target${idx} 2>&3; then true; else test_fail prepare; return; fi
    if mke2fs -q /dev/mapper/target${idx}; then true; else test_fail mke2fs; return; fi
    if ${CM} --config-dir ${TMPDIR} --release target${idx} 2>&3; then true; else test_fail release; return; fi
    test_pass
};


function test_setup_loop() {
    # basic test of prepare/release via loopback device
    if test_start "basic setup (loopback)"; then true; else return; fi
    mkkeyfile 24 sha aes-192-ecb > ${TMPDIR}/keyfile
    idx=`mkrandshort`
    cat <<EOF > ${TMPDIR}/cmtab
    target${idx} {
        dev=${TMPDIR}/loopfile
        loop=auto
        dir=${TMPDIR}/mnt
        fstype=ext2 fsoptions=defaults cipher=twofish
        keyfile=${TMPDIR}/keyfile keyhash=sha keycipher=aes-192-ecb
    }
EOF
    if ${CM} --config-dir ${TMPDIR} --password ${PASSWD} --prepare target${idx} 2>&3; then true; else test_fail prepare; return; fi
    if mke2fs -q /dev/mapper/target${idx}; then true; else test_fail mke2fs; return; fi
    if ${CM} --config-dir ${TMPDIR} --release target${idx} 2>&3; then true; else test_fail release; return; fi
    test_pass
};


function test_setup_roloop() {
    # test prepare/release of loopback on read-only device
    if test_start "read-only loopback"; then true else return; fi
    mkkeyfile 24 sha aes-192-ecb > ${TMPDIR}/keyfile
    idx=`mkrandshort`
    mkdir ${TMPDIR}/romnt
    ${DD} if=/dev/zero of=${TMPDIR}/roloopfile bs=1M count=16 2>/dev/null
    ${LOSETUP} ${LOOPDEV2} ${TMPDIR}/roloopfile
    mke2fs -q ${LOOPDEV2}
    mount -t ext2 ${LOOPDEV2} ${TMPDIR}/romnt
    ${DD} if=/dev/zero of=${TMPDIR}/romnt/lpfl bs=1M count=8 2>/dev/null
    cat <<EOF > ${TMPDIR}/cmtab
    target${idx} {
        dev=${TMPDIR}/romnt/lpfl flags=nofsck
        loop=auto
        dir=${TMPDIR}/mnt
        fstype=ext2 fsoptions=ro cipher=twofish
        keyfile=${TMPDIR}/keyfile keyhash=sha keycipher=aes-192-ecb
    }
EOF
    if ${CM} --config-dir ${TMPDIR} --password ${PASSWD} --prepare target${idx} 2>&3; then true; else test_fail prepare; return; fi
    if mke2fs -q /dev/mapper/target${idx}; then true; else test_fail mke2fs; return; fi
    if ${CM} --config-dir ${TMPDIR} --release target${idx} 2>&3; then true; else test_fail release; return; fi
    mount -o remount,ro ${TMPDIR}/romnt
    if ${CM} --config-dir ${TMPDIR} --password ${PASSWD} --mount target${idx} 2>&3; then true; else test_fail mount-ro; return; fi
    if ${CM} --config-dir ${TMPDIR} --password ${PASSWD} --unmount target${idx} 2>&3; then true; else test_fail unmount-ro; return; fi
    # ideally we should try rw-mounting the filesystem,
    # and checking that the operation fails, but libdevmapper-1.01 apparently
    # does not deal well with read-only loopback devices
    umount ${TMPDIR}/romnt
    ${LOSETUP} -d ${LOOPDEV2}
    rm ${TMPDIR}/roloopfile
    rmdir ${TMPDIR}/romnt
    test_pass
};

function test_null() {
    # test robustness to null cmtab targets
    if test_start "null targets"; then true; else return; fi
    idx=`mkrandshort`
    cat <<EOF > ${TMPDIR}/cmtab
    target${idx} {
    }
EOF
    if ${CM} --config-dir ${TMPDIR} --list 1>&3 2>&3 ; then true; else test_fail list; return; fi
    if ${CM} --config-dir ${TMPDIR} --list target${idx} 1>&3 2>&3; then true; else test_fail list; return; fi
    test_pass
};


function test_keygen() {
    # test automatic key generation
    if test_start "key generation"; then true; else return; fi
    if [ -f ${TMPDIR}/keyfile ]; then rm ${TMPDIR}/keyfile; fi
    idx=`mkrandshort`
    cat <<EOF > ${TMPDIR}/cmtab
    target${idx} {
        dev=${LOOPDEV}
        dir=${TMPDIR}/mnt
        fstype=ext2 fsoptions=defaults cipher=twofish
        keyfile=${TMPDIR}/keyfile keyhash=sha keycipher=aes-192-cbc
    }
EOF
    if su -p ${USER1} -c "${CM} --config-dir ${TMPDIR} --newpassword ${PASSWD} --generate-key 16 target${idx}" 2>&3; then test_fail "privilege violation"; return; fi
    if ${CM} --config-dir ${TMPDIR} --newpassword ${PASSWD} --generate-key 16 target${idx} 2>&3; then true; else test_fail make-key; return; fi
    if [ ! -f ${TMPDIR}/keyfile ]; then test_fail missing-key; return; fi
    if ${CM} --config-dir ${TMPDIR} --password ${PASSWD} --prepare target${idx} 2>&3; then true; else test_fail prepare; return; fi
    if ${CM} --config-dir ${TMPDIR} --release target${idx} 2>&3; then true; else test_fail release; return; fi
    if ${CM} --config-dir ${TMPDIR} --newpassword ${PASSWD} --generate-key 16 target${idx} 2>&3; then test_fail key-overwrite; return; fi
    test_pass
};


function test_passchange() {
    # test password-changing
    if test_start "password changing"; then true; else return; fi
    if [ -f ${TMPDIR}/keyfile ]; then rm ${TMPDIR}/keyfile; fi
    idx=`mkrandshort`
    NEWPASSWD="${PASSWD}-new${idx}"
    cat <<EOF > ${TMPDIR}/cmtab
    target${idx} {
        dev=${LOOPDEV}
        dir=${TMPDIR}/mnt
        fstype=ext2 fsoptions=defaults cipher=blowfish
        keyfile=${TMPDIR}/keyfile keyhash=rmd160 keycipher=des-ede-cbc
    }
EOF
    if ${CM} --config-dir ${TMPDIR} --newpassword ${PASSWD} --generate-key 16 target${idx} 2>&3; then true; else test_fail make-key; return; fi
    if ${CM} --config-dir ${TMPDIR} --password ${PASSWD} --prepare target${idx} 2>&3; then true; else test_fail prepare; return; fi
    if ${CM} --config-dir ${TMPDIR} --release target${idx} 2>&3; then true; else test_fail release; return; fi

    rm -f ${TMPDIR}/keyfile-old
    if ${CM} --config-dir ${TMPDIR} --password ${PASSWD} --newpassword ${NEWPASSWD} --change-password target${idx} 2>&3; then true; else test_fail "changing password"; return; fi
    if [ -f ${TMPDIR}/keyfile-old ]; then rm ${TMPDIR}/keyfile-old; else test_fail "missing backup key"; return; fi
    if ${CM} --config-dir ${TMPDIR} --password ${PASSWD} --prepare target${idx} 2>&3; then test_fail "old password"; return; fi
    if ${CM} --config-dir ${TMPDIR} --password ${NEWPASSWD} --prepare target${idx} 2>&3; then true; else test_fail prepare-new; return; fi
    if ${CM} --config-dir ${TMPDIR} --release target${idx} 2>&3; then true; else test_fail release-new; return; fi
    test_pass
};


function test_mtab() {
    # test of updates to mtab
    if test_start "mtab updates"; then true; else return; fi
    mkkeyfile 7 sha1 cast5-cfb > ${TMPDIR}/keyfile
    idx=`mkrandshort`
    if [ -x /sbin/mkfs.minix ]; then
        fstype=minix
    else
        fstype=ext3
    fi
    ln -s ./mnt ${TMPDIR}/mnt-link0
    ln -s mnt ${TMPDIR}/mnt-link1
    for variant in "" "/" "//" "/./" "/.//./" "-link0" "-link1"
    do
        cat <<EOF > ${TMPDIR}/cmtab
        target${idx} {
            flags=user,nofsck
            dev=${TMPDIR}/loopfile
            dir=${TMPDIR}/mnt${variant}
            fstype=${fstype} fsoptions=ro,noexec cipher=cast5
            keyfile=${TMPDIR}/keyfile keyhash=sha1 keycipher=cast5-cfb
        }
EOF
        echo "variant=\"${variant}\"" >&3
        if ${CM} --config-dir ${TMPDIR} --password ${PASSWD} --prepare target${idx} 2>&3; then true; else test_fail prepare; return; fi
        if mkfs -t ${fstype} /dev/mapper/target${idx} 1>&3 2>&3; then true; else
            ${CM} --config-dir ${TMPDIR} --release target${idx} 2>&3;
            test_fail mkfs.${fstype}; return
        fi
        if ${CM} --config-dir ${TMPDIR} --release target${idx} 2>&3; then true; else test_fail release; return; fi

        if [ `df -k | grep -c /dev/mapper/target${idx}` -ne 0 ]; then test_fail pre-existing; return; fi
        if su -p ${USER1} -c "${CM} --config-dir ${TMPDIR} --password ${PASSWD} --mount target${idx}" 2>&3; then true; else test_fail mount; return; fi
        if [ `df -k | grep -c "/dev/mapper/target${idx}"` -ne 1 ]; then test_fail unregistered; return; fi
        if su -p ${USER1} -c "${CM} --config-dir ${TMPDIR} --unmount target${idx}" 2>&3; then true; else test_fail unmount; return; fi
        if [ `df -k | grep -c /dev/mapper/target${idx}` -ne 0 ]; then test_fail remnant; return; fi
    done
    rm ${TMPDIR}/mnt-link?

    test_pass
};


function test_listing() {
    # test listing of cmtab targets
    if test_start "listing targets"; then true; else return; fi
    cat < /dev/null > ${TMPDIR}/cmtab
    tlist=""
    for tgt in 0 1 2 3 4 5 6 7
    do
        idx=`mkrandshort`
        idx2=`mkrandshort`
        cat <<EOF >> ${TMPDIR}/cmtab
            target${idx} { dev=${TMPDIR}/loopfile dir=/mnt/point-${idx2}
                fstype=brokenfs fsoptions=nosuid,noatime,sync cipher=blowfish
                keyfile=${TMPDIR}/keyfile keyhash=md5 keycipher=aes }
EOF
        tlist="${tlist} target${idx},/mnt/point-${idx2}"
    done
    if su -p ${USER1} -c "${CM} --config-dir ${TMPDIR} --password ${PASSWD} --list" > ${TMPDIR}/tlist 2>&3; then true; else test_fail listing; return; fi
    for marker in ${tlist}
    do
        tgt=`echo $marker | sed 's/^\(.*\),.*/\1/'`
        dir=`echo $marker | sed 's/^.*,\(.*\)/\1/'`
        dirq=`awk "/^${tgt}/{ printf\"%s\",\\$5 }" ${TMPDIR}/tlist`
        if [ "${dirq}" = "" ]; then test_fail absent; return; fi
        if [ "${dirq}" != "\"${dir}\"" ]; then test_fail mismatched; return; fi
    done
    rm ${TMPDIR}/tlist
    test_pass
};


function test_bad_passwd() {
    # test of password mismatch
    if test_start "basic password mismatch"; then true; else return; fi
    mkkeyfile 32 md5 aes-128-cbc > ${TMPDIR}/keyfile
    idx=`mkrandshort`
    cat <<EOF > ${TMPDIR}/cmtab
    target${idx} {
        dev=${LOOPDEV}
        dir=${TMPDIR}/mnt
        fstype=ext2 fsoptions=defaults cipher=twofish
        keyfile=${TMPDIR}/keyfile keyhash=md5 keycipher=aes-128-cbc
    }
EOF
    if ${CM} --config-dir ${TMPDIR} --password NOT${PASSWD} --prepare target${idx} 2>&3; then
        ${CM} --config-dir ${TMPDIR} --release target${idx} 2>&3
        test_fail prepare
    else
        test_pass;
    fi
};


function test_bad_keyalg() {
    # test of unavailable keycipher algorithm
    if test_start "unavailable key-cipher"; then true; else return; fi
    mkkeyfile 32 md5 aes-128-cbc > ${TMPDIR}/keyfile
    idx=`mkrandshort`
    cat <<EOF > ${TMPDIR}/cmtab
    target${idx} {
        dev=${LOOPDEV}
        dir=${TMPDIR}/mnt
        fstype=ext2 fsoptions=defaults cipher=twofish
        keyfile=${TMPDIR}/keyfile keyhash=md5 keycipher=aes-123-ebc
    }
EOF
    if ${CM} --config-dir ${TMPDIR} --password ${PASSWD} --prepare target${idx} 2>&3; then
        ${CM} --config-dir ${TMPDIR} --release target${idx} 2>&3
        test_fail prepare
    else
        test_pass;
    fi
};


function test_bad_keyhash() {
    # test of unavailable keyhash algorithm
    if test_start "unavailable key-hashing"; then true; else return; fi
    mkkeyfile 32 md5 aes-128-cbc > ${TMPDIR}/keyfile
    idx=`mkrandshort`
    cat <<EOF > ${TMPDIR}/cmtab
    target${idx} {
        dev=${LOOPDEV}
        dir=${TMPDIR}/mnt
        fstype=ext2 fsoptions=defaults cipher=twofish
        keyfile=${TMPDIR}/keyfile keyhash=md15 keycipher=aes-128-cbc
    }
EOF
    if ${CM} --config-dir ${TMPDIR} --password ${PASSWD} --prepare target${idx} 2>&3; then
        ${CM} --config-dir ${TMPDIR} --release target${idx} 2>&3
        test_fail prepare
    else
        test_pass;
    fi
};


function test_frenzy() {
    # test multiple targets being (un)mounted in parallel
    if test_start "frenetic activity"; then true; else return; fi
    mkkeyfile 32 md4 aes-128-cbc > ${TMPDIR}/keyfile
    tgtlist=""
    pos=0
    fsz=2048
    cat /dev/null > ${TMPDIR}/cmtab
    for cnt in 0 1 2 3 4 5 6 7; do
        if [ ! -d ${TMPDIR}/mnt${cnt} ]; then mkdir ${TMPDIR}/mnt${cnt}; fi
        idx=`mkrandshort`
        while ( echo ${tgtlist} | grep -q target${idx} ); do
            idx=`mkrandshort`
	done
        tgtlist="$tgtlist target${idx}"
	cat <<EOF >> ${TMPDIR}/cmtab
        target${idx} {
            dev=${LOOPDEV} startsector=${pos} numsectors=${fsz}
            dir=${TMPDIR}/mnt${cnt} flags=user,nofsck
            fstype=ext2 fsoptions=defaults cipher=blowfish
            keyfile=${TMPDIR}/keyfile keyhash=md4 keycipher=aes-128-cbc }
EOF
        pos=`expr ${pos} + ${fsz}`
    done
    if ${CM} --config-dir ${TMPDIR} --password ${PASSWD} --prepare --all 2>&3; then true; else test_fail prepare; return; fi
    for tgt in ${tgtlist}; do
        if mke2fs -q /dev/mapper/${tgt}; then true; else test_fail mke2fs; return; fi
        if ${CM} --config-dir ${TMPDIR} --release ${tgt} 2>&3; then true; else test_fail release; fi
    done
    srtlist=`echo ${tgtlist} | awk '{for (i=1; i<=NF; ++i) printf"%s\n",\$i}' | sort`
    for tgt in ${srtlist}; do
        su -p ${USER1} -c "${CM} --config-dir ${TMPDIR} --password ${PASSWD} --mount ${tgt}" 2>&3 &
    done
    wait
    cat ${TMPDIR}/cmstatus 1>&3
    if [ `wc -l ${TMPDIR}/cmstatus | awk '{printf"%d",$1}'` -ne 10 ]; then test_fail cmstatus; return; fi
    if [ `df -k | grep -c /dev/mapper/target` -lt 8 ]; then test_fail df; return; fi
    if su -p ${USER1} -c "${CM} --config-dir ${TMPDIR} --unmount --all" 2>&3; then true; else test_fail unmount; return; fi
    test_pass
};


function test_algorithms() {
    # test usability of different encryption & hashing algorithms
    if test_start "algorithm availability"; then true; else return; fi
    for keycipher in aes-128-cbc des rc4-40
    do
        for keyhash in md5 sha1
        do
            for CipherLen in aes,32 blowfish,48 serpent,16
            do
                cipher=`echo $CipherLen | sed 's/^\(.*\),.*/\1/'`
                len=`echo $CipherLen | sed 's/^.*,\(.*\)/\1/'`
                echo "keycipher=${keycipher} keyhash=${keyhash} cipher=${CipherLen}" 1>&3

                mkkeyfile ${len} ${keyhash} ${keycipher} > ${TMPDIR}/keyfile
                idx=`mkrandshort`
                cat <<EOF > ${TMPDIR}/cmtab
                    target${idx} {
                        flags=user,nofsck
                        dev=${TMPDIR}/loopfile
                        dir=${TMPDIR}/mnt
                        fstype=ext3 fsoptions=noatime,sync cipher=${cipher}
                        keyfile=${TMPDIR}/keyfile keyhash=${keyhash}
                        keycipher=${keycipher}
                    }
EOF
                if ${CM} --config-dir ${TMPDIR} --password ${PASSWD} --prepare target${idx} 2>&3; then true; else test_fail prepare; return; fi
                if mke2fs -q -j /dev/mapper/target${idx}; then true; else test_fail mke2fs; return; fi
                if ${CM} --config-dir ${TMPDIR} --release target${idx} 2>&3; then true; else test_fail release; return; fi

                if su -p ${USER1} -c "${CM} --config-dir ${TMPDIR} --password ${PASSWD} --mount target${idx}" 2>&3; then true; else test_fail mount; return; fi
                if su -p ${USER1} -c "${CM} --config-dir ${TMPDIR} --unmount target${idx}" 2>&3; then true; else test_fail unmount; return; fi

            done
        done
    done
    test_pass
};


function test_mountlock() {
    # test of mounting & user-locking
    if test_start "mounting & user-locking"; then true; else return; fi
    idx=`mkrandshort`
    cat <<EOF > ${TMPDIR}/cmtab
    target${idx} {
	flags=user,nofsck
        dev=${TMPDIR}/loopfile
        dir=${TMPDIR}/mnt
        fstype=ext3 fsoptions=nosuid,noexec cipher=twofish
        keyfile=${TMPDIR}/keyfile keyhash=sha1 keycipher=cast5-cfb
    }
EOF
    rm -f ${TMPDIR}/keyfile
    if ${CM} --config-dir ${TMPDIR} --generate-key 32 --newpassword ${PASSWD} target${idx} 2>&3; then true; else test_fail make-key; return; fi
    if ${CM} --config-dir ${TMPDIR} --password ${PASSWD} --prepare target${idx} 2>&3; then true; else test_fail prepare; return; fi
    if mke2fs -q -j /dev/mapper/target${idx}; then true; else test_fail mke2fs; return; fi
    if ${CM} --config-dir ${TMPDIR} --release target${idx} 2>&3; then true; else test_fail release; return; fi

    if su -p ${USER1} -c "${CM} --config-dir ${TMPDIR} --password ${PASSWD} --mount target${idx}" 2>&3; then true; else test_fail mount; return; fi
    if su -p ${USER2} -c "${CM} --config-dir ${TMPDIR} --unmount target${idx}" 2>&3; then test_fail bad-unmount; return; fi
    if su -p ${USER1} -c "${CM} --config-dir ${TMPDIR} --unmount target${idx}" 2>&3; then true; else test_fail unmount; return; fi

    test_pass
};


function test_userflags() {
    # test of mounting with user/nouser flags
    if test_start "mounting & user-flags"; then true; else return; fi
    mkkeyfile 24 sha1 cast5-cfb > ${TMPDIR}/keyfile
    idx=`mkrandshort`
    cat <<EOF > ${TMPDIR}/cmtab
    target${idx} {
	flags=nouser,nofsck
        dev=${TMPDIR}/loopfile
        dir=${TMPDIR}/mnt
        fstype=ext3 fsoptions=nosuid,noexec cipher=twofish
        keyfile=${TMPDIR}/keyfile keyhash=sha1 keycipher=cast5-cfb
    }
EOF
    if ${CM} --config-dir ${TMPDIR} --password ${PASSWD} --prepare target${idx} 2>&3; then true; else test_fail prepare; return; fi
    if mke2fs -q -j /dev/mapper/target${idx}; then true; else test_fail mke2fs; return; fi
    if ${CM} --config-dir ${TMPDIR} --release target${idx} 2>&3; then true; else test_fail release; return; fi

    for cfg in user,${USER1},pass user,root,pass nouser,${USER1},fail nouser,root,pass
    do
        flgs=`echo $cfg | sed 's%^\([^,]*\),.*%\1%'`
        usr=`echo $cfg | sed 's%.*,\(.*\),.*%\1%'`
        exp=`echo $cfg | sed 's%[^,]*,[^,]*,\(.*\)%\1%'`
        ed -s ${TMPDIR}/cmtab <<EOF 2>/dev/null 1>&2
/flags=/
c
flags=${flgs},nofsck
.
w
q
EOF
        echo "config: $cfg" 1>&3
        su -p ${usr} -c "${CM} --config-dir ${TMPDIR} --password ${PASSWD} --mount target${idx}" 2>&3
        stat=$?
        if [ \( "$stat" -eq 0 -a "$exp" != "pass" \) -o \( "$stat" -ne 0 -a "$exp" != "fail" \) ]; then
            test_fail bad-mount
            return
        fi
        su -p ${usr} -c "${CM} --config-dir ${TMPDIR} --password ${PASSWD} --unmount target${idx}" 2>&3
        stat=$?
        if [ \( "$stat" -eq 0 -a "$exp" != "pass" \) -o \( "$stat" -ne 0 -a "$exp" != "fail" \) ]; then
            test_fail bad-unmount
            return
        fi
    done

    test_pass
};


function test_mountsynonyms() {
    # test for synonyms of (un)mount
    if test_start "mount synonyms"; then true; else return; fi
    mkkeyfile 16 md4 aes192 > ${TMPDIR}/keyfile
    idx=`mkrandshort`
    cat <<EOF > ${TMPDIR}/cmtab
    target${idx} {
        flags=user,nofsck
        dev=${TMPDIR}/devfile
        dir=${TMPDIR}/mnt
        fstype=ext3 fsoptions=,,,noatime cipher=blowfish
        keyfile=${TMPDIR}/keyfile keyhash=md4 keycipher=aes192
    }
EOF
    if ${CM} --config-dir ${TMPDIR} --password ${PASSWD} --prepare target${idx} 2>&3; then true; else test_fail prepare; return; fi
    if mke2fs -q -j /dev/mapper/target${idx}; then true; else test_fail mke2fs; return; fi
    if ${CM} --config-dir ${TMPDIR} --release target${idx} 2>&3; then true; else test_fail release; return; fi
    for mntopt in "" "-m" "--mount"
    do
        for unmopt in "-u" "--unmount"
        do
            echo "mount[${mntopt}] unmount[${unmopt}]" 1>&3
            if su -p ${USER1} -c "${CM} --config-dir ${TMPDIR} --password ${PASSWD} ${mntopt} target${idx}" 2>&3; then true; else test_fail mount; return; fi
            if su -p ${USER1} -c "${CM} --config-dir ${TMPDIR} ${unmopt} target${idx}" 2>&3; then true; else test_fail unmount; return; fi

        done
    done
    test_pass
};


function test_offsets() {
    # check if startsector/numsectors parameters operate correctly
    if test_start "block offsets"; then true; else return; fi
    mkkeyfile 16 md4 des > ${TMPDIR}/keyfile
    for offset in 0 16 256
    do
        for length in 128 512 2048
        do
            idx=`mkrandshort`
            cat <<EOF > ${TMPDIR}/cmtab
                target${idx} {
                    flags=user,nofsck
                    dev=${TMPDIR}/devfile
                    startsector=${offset}
                    numsectors=${length}
                    dir=${TMPDIR}/mnt
                    fstype=ext2 fsoptions=defaults
                    cipher=aes ivoffset=16
                    keyfile=${TMPDIR}/keyfile keyhash=md4
                    keycipher=des
                }
EOF
            ${DD} if=/dev/zero of=${LOOPDEV} 2>/dev/null
            if ${CM} --config-dir ${TMPDIR} --password ${PASSWD} --prepare target${idx} 2>&3; then
                ${DD} if=/dev/zero of=/dev/mapper/target${idx} bs=1b count=`expr ${length} + 16` 2>&3
                locs=`${TMPDIR}/bingrep < ${LOOPDEV}`
                first=`echo $locs | awk '{printf"%d",($1 / 512)}'`
                extent=`echo $locs | awk '{printf"%d", ($2 - $1) / 512}'`
                if [ "${first}" -ne "${offset}" -o "${extent}" -ne "${length}" ]; then
                    test_fail "offset/length mismatch"
                    return
                fi
                ${CM} --config-dir ${TMPDIR} --release target${idx} 2>&3
            else
                test_fail prepare
                return
            fi
        done
    done
    test_pass
};


function test_swap() {
    # basic test of swapon/swapoff on raw device
    if test_start "swapon (device)"; then true; else return; fi
    for cfg in ext2,mkswap,fail swap,mkswap,pass swap,nomkswap,fail
    do
        fst=`echo $cfg | sed 's%^\([^,]*\),.*%\1%'`
        flg=`echo $cfg | sed 's%.*,\(.*\),.*%\1%'`
        exp=`echo $cfg | sed 's%[^,]*,[^,]*,\(.*\)%\1%'`

        idx=`mkrandshort`
        cat <<EOF > ${TMPDIR}/cmtab
        swap${idx} {
            dev=${LOOPDEV}
            fstype=${fst} flags=${flg} cipher=twofish
            keyfile=/dev/urandom keymaxlen=32 keycipher=none
        }
EOF
        echo "config: $cfg" 1>&3
        if su -p ${USER1} -c "${CM} --config-dir ${TMPDIR} --password ${PASSWD} --swapon swap${idx}" 2>&3; then test_fail privilege; return; fi
        if grep -q swap${idx} /proc/swaps; then test_fail pre-existing; return; fi
        ${CM} --config-dir ${TMPDIR} --password ${PASSWD} --swapon swap${idx} 2>&3;
	stat=$?
	echo "stat: $stat" 1>&3
	if [ \( "$stat" -eq 0 -a "$exp" != "pass" \) -o \( "$stat" -ne 0 -a "$exp" != "fail" \) ]; then
	    test_fail swapon
	    return
	fi
	if [ "$stat" -eq 0 ]; then
            if grep -q swap${idx} /proc/swaps; then true; else test_fail proc+swaps; return; fi
	fi
        ${CM} --config-dir ${TMPDIR} --password ${PASSWD} --swapoff swap${idx} 2>&3;
	if [ \( "$stat" -eq 0 -a "$exp" != "pass" \) -o \( "$stat" -ne 0 -a "$exp" != "fail" \) ]; then
            test_fail swapoff
	    return
	fi
	if [ "$stat" -eq 0 ]; then
            if grep -q swap${idx} /proc/swaps; then test_fail proc-swaps; return; fi
	fi
    done

    test_pass
};


function test_privblock() {
    # test blockage of privileged actions
    if test_start "privilege checks"; then true; else return; fi
    if [ -f ${TMPDIR}/keyfile ]; then rm ${TMPDIR}/keyfile; fi
    idx=`mkrandshort`
    cat <<EOF > ${TMPDIR}/cmtab
    target${idx} {
        dev=${LOOPDEV}
        dir=${TMPDIR}/mnt
        fstype=swap flags=mkswap cipher=twofish
        keyfile=${TMPDIR}/keyfile keyhash=sha keycipher=aes-192-cbc
    }
EOF
    COMMAND="${CM} --config-dir ${TMPDIR} --newpassword ${PASSWD} --generate-key 16 target${idx}"
    if su -p ${USER1} -c "${COMMAND}" 2>&3; then test_fail "key-generation"; return; fi
    if ${COMMAND} 2>&3; then true; else test_fail "key-generation (priv)"; return; fi

    for action in --prepare --release --swapon --swapoff
    do
        COMMAND="${CM} --config-dir ${TMPDIR} --password ${PASSWD} ${action} target${idx}"
        if su -p ${USER1} -c "${COMMAND}" 2>&3; then test_fail "${action}"; return; fi
        if ${COMMAND} 2>&3; then true; else test_fail "${action} (priv)"; return; fi
    done

    cp ${TMPDIR}/cmtab ${TMPDIR}/cmstrm
    cat /dev/null > ${TMPDIR}/cmtab
    COMMAND="${CM} --config-dir ${TMPDIR} --password ${PASSWD} --config-fd 5 --prepare target${idx}"
    if su -p ${USER1} -c "${COMMAND}" 5< ${TMPDIR}/cmstrm 2>&3; then test_fail "config-fd"; return; fi
    if ${COMMAND} 5< ${TMPDIR}/cmstrm 2>&3; then true; else test_fail "config-fd (priv)"; return; fi
    COMMAND="${CM} --config-dir ${TMPDIR} --password ${PASSWD} --config-fd 7 --release target${idx}"
    if su -p ${USER1} -c "${COMMAND}" 7< ${TMPDIR}/cmstrm 2>&3; then test_fail "config-fd"; return; fi
    if ${COMMAND} 7< ${TMPDIR}/cmstrm 2>&3; then true; else test_fail "config-fd (priv)"; return; fi
    rm ${TMPDIR}/cmstrm

    test_pass
};


function test_cscompat() {
    # check compatibility with cryptsetup
    if test_start "cryptsetup compatibility"; then true; else return; fi
    if which cryptsetup 1>&3; then true; else test_fail "not available"; return; fi
    mkkeyfile 32 md5 aes192 > ${TMPDIR}/keyfile
    idx=`mkrandshort`
    for cipher in blowfish serpent
    do
        for length in 4096 8192
        do
            for startsec in 0 32
            do
                for ivoffset in 0 172 932
                do
                    echo "${cipher},${length},${startsec},${ivoffset}" 1>&3
                    openssl enc -d -aes192 -md md5 -in ${TMPDIR}/keyfile -pass pass:${PASSWD}| \
                    cryptsetup -d /dev/stdin -c ${cipher} -b ${length} -o ${startsec} -p ${ivoffset} create cstarget${idx} ${LOOPDEV} 2>&3 
                    if [ -b /dev/mapper/cstarget${idx} ]; then
                        mke2fs -q -j /dev/mapper/cstarget${idx}
                        cryptsetup remove cstarget${idx}
                    else
                        test_fail cryptsetup
                        return
                    fi
                    cat <<EOF > ${TMPDIR}/cmtab
                    target${idx} {
                        flags=user,nofsck
                        dev=${LOOPDEV}
                        startsector=${startsec} numsectors=${length}
                        dir=${TMPDIR}/mnt
                        fstype=ext3 fsoptions=defaults
                        cipher=${cipher} ivoffset=${ivoffset}
                        keyfile=${TMPDIR}/keyfile keyhash=md5 keycipher=aes192
                    }
EOF
                    if ${CM} --config-dir ${TMPDIR} --password ${PASSWD} target${idx} 2>&3; then true; else test_fail mount; return; fi
                    if ${CM} --config-dir ${TMPDIR} --password ${PASSWD} --unmount target${idx} 2>&3; then true; else test_fail unmount; return; fi
                done
            done
        done
    done
    test_pass
};


function test_loopset() {
    # check that 'loopdev' parameter correction targets specific loopback dev
    if test_start "loopdev specification"; then true; else return; fi
    mkkeyfile 16 md4 bf-ecb > ${TMPDIR}/keyfile
    idx=`mkrandshort`
    for ldev in /dev/loop{3,6,1,0,7,4,2,5}
    do
        if ${LOSETUP} $ldev >/dev/null 2>&1; then
            # loop-device is already in use
            true
        else
            cat <<EOF > ${TMPDIR}/cmtab
                target${idx} {
                    dev=${TMPDIR}/loopfile
                    loop=${ldev}
                    dir=${TMPDIR}/mnt
                    fstype=ext2 fsoptions=,,,ro,,,noatime cipher=twofish
                    keyfile=${TMPDIR}/keyfile keyhash=md4
                    keycipher=bf-ecb
                }
EOF
            if ${CM} --config-dir ${TMPDIR} --password ${PASSWD} --prepare target${idx} 2>&3; then true; else test_fail prepare; return; fi
            if ${LOSETUP} $ldev 1>&3 2>&3; then
                if ${CM} --config-dir ${TMPDIR} --password ${PASSWD} --release target${idx} 2>&3; then true; else test_fail release; return; fi
            else
                ${CM} --config-dir ${TMPDIR} --password ${PASSWD} --release target${idx} 2>&3
                test_fail "loopback unconfigured";
                return
            fi
        fi
    done
    test_pass
};

function test_residues() {
    # check if any zombie device-mapper targets have been created
    if test_start "device-mapper residue targets"; then true; else return; fi
    sleep 1	# give time for old targets to die
    dmsetup ls | grep '^target' > ${TMPDIR}/dm-list1
    if cmp -s "${TMPDIR}/dm-list0" "${TMPDIR}/dm-list1"; then
        test_pass
    else
        test_fail
        for tgt in `awk '{printf"%s\n",$1}' ${TMPDIR}/dm-list1`
        do
            if grep -q "${tgt}" ${TMPDIR}/dm-list0; then true; else
                echo "removing ${tgt}"
                umount "/dev/mapper/${tgt}" 2>&3
                dmsetup remove ${tgt} 2>&3
            fi
        done
    fi
    rm "${TMPDIR}/dm-list1"
};


#
# main program
#


# prepare log-file:
exec 3> mudslinger.log
uname -a >&3
date >&3
if [ -r /usr/include/linux/version.h ]; then
    cat /usr/include/linux/version.h >&3
fi

if [ ! -d ${TMPDIR} ]; then
    mkdir ${TMPDIR} ${TMPDIR}/mnt
else
    echo "${TMPDIR} already exists - exiting"
    exit 1
fi

if [ ! -u ${CM} ]; then
    chown root ${CM}
    chmod u+s ${CM}
fi

# prepare loopback file & pseudo device file:
set -e
touch ${TMPDIR}/keyfile
${DD} if=/dev/zero of=${TMPDIR}/loopfile bs=1M count=64 2>&3 1>&2
${DD} if=/dev/zero of=${TMPDIR}/devfile bs=1M count=64 2>&3 1>&2
${LOSETUP} ${LOOPDEV} ${TMPDIR}/devfile
set +e

# keep record of existing device-mapper targets
dmsetup ls | grep '^target' > ${TMPDIR}/dm-list0

# prepare binary-search tool:
mkbingrep ${TMPDIR}/bingrep

# run all tests:
test_version
test_binary
test_setup_dev
test_setup_loop
test_setup_roloop
test_null
test_keygen
test_passchange
test_mtab
test_listing
test_bad_passwd
test_bad_keyalg
test_bad_keyhash
test_mountlock
test_userflags
test_frenzy
test_algorithms
test_mountsynonyms
test_offsets
test_loopset
test_swap
test_privblock
test_cscompat
test_residues

test_summary

${LOSETUP} -d ${LOOPDEV}
rm -f ${TMPDIR}/loopfile ${TMPDIR}/devfile ${TMPDIR}/keyfile \
    ${TMPDIR}/cmtab ${TMPDIR}/cmstatus \
    ${TMPDIR}/dm-list0 ${TMPDIR}/bingrep
rmdir ${TMPDIR}/mnt* ${TMPDIR}

exit 0
