#!/bin/sh

CRYPT=${CRYPT:-./crypt}
ASKPASS=${ASKPASS:-./askpass}

oneTimeSetUp() {
    export OUTDIR="$SHUNIT_TMPDIR/out"
    export BINDIR="$SHUNIT_TMPDIR/bin"
    export PATH="$BINDIR:$PATH"

    # Directory for output data (dropped after each test)
    mkdir -p $OUTDIR

    # Test helper scripts
    mkdir -p $BINDIR
    cat >$BINDIR/askpass <<END
#!/bin/sh
echo -n "\${FAKE_ASKPASS_ANSWER:-foobar}"
END
    cat >$BINDIR/cryptsetup <<END
#!/bin/sh
echo "\$@" >>$OUTDIR/cryptsetup
END
    chmod 755 $BINDIR/*
}

setUp() {
    # Overrides for askpass
    export DIVERTED_ASKPASS="$BINDIR/askpass"
    export CRYPT_HELPER="$CRYPT"
    export NUKE_PASSWORD_HASH_PATH="$SHUNIT_TMPDIR/password_hash"
    export CRYPTTAB_SOURCE="$SHUNIT_TMPDIR/device"
    touch $CRYPTTAB_SOURCE

    # Clean up some environment variables that might be set by tests
    unset FAKE_ASKPASS_ANSWER
}

tearDown() {
    if [ -d "$OUTDIR" ]; then
	rm -f $OUTDIR/*
    fi
    rm -f $NUKE_PASSWORD_HASH_PATH
}

testCryptNoArgsOutput() {
    output=$($CRYPT 2>&1 </dev/null)
    if ! echo "$output" | grep -q "Usage:"; then
	fail 'Does not print Usage output without argument'
    fi
}
testCryptNoArgsExitCode() {
    $CRYPT >/dev/null 2>&1 </dev/null
    assertEquals 'Bad exit code when ran without argument' 1 $?
}

testCryptBadArgOutput() {
    output=$($CRYPT --doesnotexist 2>&1 </dev/null)
    if ! echo "$output" | grep -q "Usage:"; then
	fail 'Does not print Usage output with invalid argument'
    fi
}
testCryptBadArgExitCode() {
    $CRYPT --doesnotexist >/dev/null 2>&1 </dev/null
    assertEquals 'Bad exit code when ran with invalid argument' 1 $?
}

testCryptHelpOutput() {
    output=$($CRYPT --help 2>&1 </dev/null)
    if ! echo "$output" | grep -q "Usage:"; then
	fail 'Does not print Usage output with --help'
    fi
}
testCryptHelpExitCode() {
    $CRYPT --help >/dev/null 2>&1 </dev/null
    assertEquals 'Bad exit code when ran with --help' 0 $?
}

testCryptGenerateEmptyPassword() {
    output_stdout=$($CRYPT --generate 'h/' 2>/dev/null </dev/null)
    output_stderr=$($CRYPT --generate 'h/' 2>&1 >/dev/null </dev/null)
    exit_code=$?
    assertNull "'crypt --generate </dev/null' unexpectedly generated something" "$output_stdout"
    if ! echo "$output_stderr" | grep -q "ERROR:"; then
	fail "'crypt --generate </dev/null' did not print any error message"
    fi
    assertEquals "'crypt --generate </dev/null' has a bad exit code" 1 $exit_code
}

do_testCryptGenerate() {
    output=$(echo "foobar" | $CRYPT --generate $1)
    exit_code=$?
    assertNotNull "'echo foobar | crypt --generate $1' ($2) provided no output" "$output"
    assertEquals "'echo foobar | crypt --generate $1' ($2) returned bad exit code" 0 $exit_code
}

testCryptGenerateBasic() {
    do_testCryptGenerate 'h/' 'DES'
}
testCryptGenerateMD5() {
    do_testCryptGenerate '$1$h/$' 'MD5'
}
testCryptGenerateSHA256() {
    do_testCryptGenerate '$5$h/$' 'SHA-256'
}
testCryptGenerateSHA512() {
    do_testCryptGenerate '$6$h/$' 'SHA-512'
}

testCryptGenerateBadSalt() {
    output_stdout=$(echo foobar | $CRYPT --generate '$999$foobar$' 2>/dev/null)
    output_stderr=$(echo foobar | $CRYPT --generate '$999$foobar$' 2>&1 >/dev/null)
    exit_code=$?
    assertNull "'echo foobar | crypt --generate <bad-salt>' unexpectedly generated something" "$output_stdout"
    if ! echo "$output_stderr" | grep -q "ERROR:"; then
	fail "'echo foobar | crypt --generate <bad-salt>' did not print any error message"
    fi
    assertEquals "'echo foobar | crypt --generate <bad-salt>' has a bad exit code" 1 $exit_code
}

testCryptGenerateNoSaltSupplied() {
    output_stdout=$(echo foobar | $CRYPT --generate 2>/dev/null)
    output_stderr=$(echo foobar | $CRYPT --generate 2>&1 >/dev/null)
    exit_code=$?
    assertNull "'echo foobar | crypt --generate' generated noise on stderr" "$output_stderr"
    if ! echo "$output_stdout" | grep -q '^\$6\$'; then
	fail "'echo foobar | crypt --generate' did not generate a SHA-512 based hash ($output_stdout)"
    fi
    assertEquals "'echo foobar | crypt --generate' has a bad exit code" 0 $exit_code
}

testCryptGenerateEmptySaltSupplied() {
    output_stdout=$(echo foobar | $CRYPT --generate '' 2>/dev/null)
    output_stderr=$(echo foobar | $CRYPT --generate '' 2>&1 >/dev/null)
    exit_code=$?
    assertNull "'echo foobar | crypt --generate ''' generated noise on stderr" "$output_stderr"
    if ! echo "$output_stdout" | grep -q '^\$6\$'; then
	fail "'echo foobar | crypt --generate ''' did not generate a SHA-512 based hash ($output_stdout)"
    fi
    assertEquals "'echo foobar | crypt --generate ''' has a bad exit code" 0 $exit_code
}

testCryptGenerateNoSaltRandomness() {
    output1=$(echo foobar | $CRYPT --generate 2>/dev/null)
    output2=$(echo foobar | $CRYPT --generate 2>/dev/null)
    salt1="$(echo $output1 | cut -d$ -f1-3)"'$'
    salt2="$(echo $output2 | cut -d$ -f1-3)"'$'
    assertNotEquals "Two consecutive runs of 'echo foobar | $CRYPT --generate' generated the same salt" "$output1" "$output2"
    output3=$(echo foobar | $CRYPT --generate "$salt1")
    assertEquals "'echo foobar | $CRYPT --generate <salt>' did not recreate the original hash" "$output1" "$output3"
}

testCryptCheckEmptyPassword() {
    output_stdout=$($CRYPT --check 'h/GdiFWQsXxA.' 2>/dev/null </dev/null)
    output_stderr=$($CRYPT --check 'h/GdiFWQsXxA.' 2>&1 >/dev/null </dev/null)
    exit_code=$?
    assertNull "'crypt --check <hash> </dev/null' unexpectedly generated something" "$output_stdout"
    if ! echo "$output_stderr" | grep -q "ERROR:"; then
	fail "'crypt --check <hash> </dev/null' did not print any error message"
    fi
    assertEquals "'crypt --check <hash> </dev/null' has a bad exit code" 1 $exit_code
}

testCryptCheckNoHashSupplied() {
    output_stdout=$(echo "foobar" | $CRYPT --check 2>/dev/null)
    output_stderr=$(echo "foobar" | $CRYPT --check 2>&1 >/dev/null)
    if ! echo "$output_stderr" | grep -q "ERROR:"; then
	fail "'echo foobar | crypt --check' did not print any error message"
    fi
    assertNull "'echo foobar | crypt --check' unexpectly returned something on stdout" "$output_stdout"
}

testCryptCheckGoodPassword() {
    output=$(echo "foobar" | $CRYPT --check '$6$dkcZzIkv$Ju7XCIc4igWvht3bOu266vvRam6IdnIFxoyonDt.6JZl8NfCaukACeIRYVW7WQtrUtqN2TrWSgEFnXumuTiN41' 2>&1)
    exit_code=$?
    assertNull "'echo foobar | $CRYPT --check <good-hash>' printed unexpected output" "$output"
    assertEquals "'echo foobar | $CRYPT --check <good-hash>' did not exit with" 0 $exit_code
}

testCryptCheckBadPassword() {
    output=$(echo "foobar-bad" | $CRYPT --check '$6$dkcZzIkv$Ju7XCIc4igWvht3bOu266vvRam6IdnIFxoyonDt.6JZl8NfCaukACeIRYVW7WQtrUtqN2TrWSgEFnXumuTiN41' 2>&1)
    exit_code=$?
    assertNull "'echo foobar | $CRYPT --check <bad-hash>' printed unexpected output" "$output"
    assertEquals "'echo foobar | $CRYPT --check <bad-hash>' did not exit with" 1 $exit_code
}

testCryptGenerateCheckRoundtrip() {
    for salt in '' 'h/' '$1$abcd$' '$5$12345678$' '$6$deadbeef$'
    do
	password="haX0rd3ad"

	password_hash=$(echo $password | $CRYPT --generate "$salt")
	exit_code=$?
	assertEquals "'echo $password | $CRYPT --generate $salt' did not exit with" 0 $exit_code

	echo $password | $CRYPT --check "$password_hash"
	exit_code=$?
	assertEquals "'echo $password | $CRYPT --check $password_hash' did not exit with" 0 $exit_code
    done
}

testAskPassWarnsAboutMissingCrypttabSource() {
    export CRYPTTAB_SOURCE=/does/not/exist

    $ASKPASS >$OUTDIR/log 2>&1

    if ! grep -q 'WARNING: $CRYPTTAB_SOURCE' $OUTDIR/log; then
	fail "askpass should complain of missing CRYPTTAB_SOURCE"
    fi
}

testAskPassWarnsAboutMissingCryptHelper() {
    export CRYPT_HELPER=/does/not/exist

    $ASKPASS >$OUTDIR/log 2>&1

    if ! grep -q "WARNING: $CRYPT_HELPER" $OUTDIR/log; then
	fail "askpass should complain of missing \$CRYPT_HELPER"
    fi
}

testAskPassCallsCryptsetupErase() {
    # Setup the password and its matching hash
    export FAKE_ASKPASS_ANSWER="foobar"
    echo '$6$dkcZzIkv$Ju7XCIc4igWvht3bOu266vvRam6IdnIFxoyonDt.6JZl8NfCaukACeIRYVW7WQtrUtqN2TrWSgEFnXumuTiN41' >$NUKE_PASSWORD_HASH_PATH

    $ASKPASS >$OUTDIR/log 2>&1

    touch $OUTDIR/cryptsetup
    if ! grep -q "erase $CRYPTTAB_SOURCE" $OUTDIR/cryptsetup; then
	echo "Output of askpass:"
	cat $OUTDIR/log
	echo ""
	echo "Cryptsetup log:"
	cat $OUTDIR/cryptsetup
	fail "cryptsetup erase has not been called by askpass"
    fi
}

testAskPassWithoutPasswordHash() {
    # No password_hash is created

    $ASKPASS >$OUTDIR/log 2>&1

    assertFalse 'cryptsetup was unexpectly run' "[ -e $OUTDIR/cryptsetup ]"
}

testAskPassWithNonMatchingPasswordHash() {
    # Setup the password and a non-matching hash
    export FAKE_ASKPASS_ANSWER="this-is-not-the-good-password"
    echo '$6$dkcZzIkv$Ju7XCIc4igWvht3bOu266vvRam6IdnIFxoyonDt.6JZl8NfCaukACeIRYVW7WQtrUtqN2TrWSgEFnXumuTiN41' >$NUKE_PASSWORD_HASH_PATH

    $ASKPASS >$OUTDIR/log 2>&1

    assertFalse 'cryptsetup was unexpectly run' "[ -e $OUTDIR/cryptsetup ]"
}

testAskPassReturnsPassword() {
    export FAKE_ASKPASS_ANSWER="my-password"

    OUT=$($ASKPASS 2>/dev/null)

    assertEquals "askpass did not print the password" "$FAKE_ASKPASS_ANSWER" "$OUT"
}

. shunit2
