
use twtools;

package integritycheck;


######################################################################
# One time module initialization goes in here...
#
BEGIN 
{
    $description = "integrity check test";

	# This is the root directory we will be integrity checking
	#
	$root = "$twtools::twcwd/$twtools::twrootdir/ic-test-a";
	$root2 = "$twtools::twcwd/$twtools::twrootdir/ic-test-b";

	# Here are the names of the report files this test will create
	#
	$report1 = "$twtools::twcwd/$twtools::twrootdir/report/ic-1.twr";

	$report2 = "$twtools::twcwd/$twtools::twrootdir/report/dbupdate-2.twr";
	$report3 = "$twtools::twcwd/$twtools::twrootdir/report/dbupdate-3.twr";
	$report4 = "$twtools::twcwd/$twtools::twrootdir/report/dbupdate-4.twr";
}

######################################################################
# PolicyFileString -- return the policy text as a string
#
sub PolicyFileString
{
	return <<POLICY_END;	
    # Policy file generated by integrity checks test
    #
    (rulename="RuleA", severity=30, emailto="elvis@mars")
    {
        $root -> \$(ReadOnly) +S -ab; #read only plus SHA-1 minus atime & blocks
    }

    (rulename="RuleB", severity=200, emailto="elvis@mars")
    {
        $root2 -> \$(ReadOnly) +S -ab; #read only plus SHA-1 minus atime & blocks
    }

POLICY_END

}

######################################################################
# CreateFile -- create a file with the specified contents
#   
# input:  path     -- path to the file; relative to $root
#         contents -- string to put in the file
#
sub CreateFile
{
	my ($path, $contents) = @_;
	
	system( "echo $contents > $path" );

	$? && die "Create file failed for $path\n";
}

######################################################################
# RemoveFile -- removes the named file
#   
sub RemoveFile
{
	my ($path) = @_;
	
	if( -e "$path" )
	{
		system( "rm -f $path" );
	}
	
	$? && die "Remove file failed for $root/$path\n";
}


######################################################################
# CreateDir -- create a directory
#
sub CreateDir
{
	my($dir) = @_;

	# NOTE: mkdir fails if it is already a directory!
	#
	if( ! -d "$dir" )
	{
		system( "rm -f $dir" );
		system( "mkdir -p $dir" );
	
		$? && die "Mkdir failed for $root/$dir\n";
	}
}

######################################################################
# MoveFile -- move a file from one place to another
#             NOTE: file names are relative to $root
#   
# input:  old_name -- name of file to move
#         new_name -- where it should be moved to
#
sub MoveFile
{
	my($old, $new) = @_;
	
	system( "mv $old $new" );
	$? && die "mv $old $new failed!\n";
}

######################################################################
# PrintDatabase
#
sub PrintDatabase
{
	system( "$twtools::twrootdir/bin/twprint -m d -c $twtools::twrootdir/tw.cfg" );
}

######################################################################
# PrintReport
#
sub PrintReport
{
	my ($report) = @_;
	system( "$twtools::twrootdir/bin/twprint -m r -c $twtools::twrootdir/tw.cfg -r $report" );
}

######################################################################
# PrepareForTest -- creates the files that each test will be 
#                   integrity checking and initializes the database.
#
sub PrepareForTest
{
	# make sure we are cleaned up...
	#
	cleanup();

	# Make the files we will be using...
	#
    CreateDir ( "$root");
	CreateDir ( "$root/subdir" );
	CreateFile( "$root/subdir/modify.txt", "hello world" );
	CreateFile( "$root/copy-src.txt",     "asdf" );

    CreateDir ( "$root2");
	CreateFile( "$root2/deleteme.txt",     "goodbye cruel world" );

	# Initialize the database
	#
	twtools::InitializeDatabase();
}


######################################################################
#
# Run the test.
#
sub run
{
    twtools::logStatus("*** Beginning $description\n");
    printf("%-30s", "-- $description");

    if ($^O eq "dragonfly") {
        ++$twtools::twskippedtests;
        print "SKIPPED; TODO: DragonflyBSD has fewer expected changes here; refactor so we can test for correct values\n";
        return;
    }

    my $twpassed = 1;
    my $dir_mods = ($^O eq "skyos") ? 0 : 1;

	PrepareForTest();

	# make some violations...
	#
	MoveFile  ( "$root/copy-src.txt", "$root/copy-dest.txt" );
	CreateFile( "$root/subdir/modify.txt", "bark bark bark" );
    RemoveFile( "$root2/deleteme.txt");

    #######################################################
	# First run a full IC
	#
	twtools::RunIntegrityCheck();

	# Make sure we got 6 violations: 3 mod, 1 add, 2 rm.
	#
    my ($n, $a, $r, $c) = twtools::AnalyzeReport( twtools::RunReport() );
    my $n_expected = $dir_mods ? 6 : 4;
    my $c_expected = $dir_mods ? 3 : 1;

	if( ($n != $n_expected) || ($a != 1) || ($r != 2) || ($c != $c_expected) )
	{
	    twtools::logStatus("Full IC failed: $n $a $r $c\n");
	    $twpassed = 0;
	}

    # TODO: Make RunReport+AnalyzeReport play nice with signed report files
    #######################################################
    # Do it again, but sign it this time.
    #
    #twtools::RunIntegrityCheck({trailing_opts => "-E -P $twtools::twlocalpass"});

    # Make sure we got 6 violations: 3 mod, 1 add, 2 rm.
    #
    #my ($n, $a, $r, $c) = twtools::AnalyzeReport( twtools::RunReport() );

    #if( ($n != 6) || ($a != 1) || ($r != 2) || ($c != 3) )
    #{
    #    twtools::logStatus("Full IC with signing failed: $n $a $r $c\n");
    #    $twpassed = 0;
    #}

    #######################################################
    # Now run 'just' the FS section, aka the whole policy
    #
    RemoveFile("$reportloc");
    twtools::RunIntegrityCheck(trailing_opts => "-x FS");

	# Make sure we got 6 violations: 3 mod, 1 add, 2 rm.
    #
    my ($n, $a, $r, $c) = twtools::AnalyzeReport( twtools::RunReport() );
    my $n_expected = $dir_mods ? 6 : 4;
    my $c_expected = $dir_mods ? 3 : 1;

    if( ($n != $n_expected) || ($a != 1) || ($r != 2) || ($c != $c_expected) )
    {
        twtools::logStatus("IC with FS section failed: $n $a $r $c\n");
        $twpassed = 0;
    }

    #######################################################
    # Now run a check ignoring the SHA attribute, should still return same changes
    #
    RemoveFile("$reportloc");
    twtools::RunIntegrityCheck(trailing_opts => "-i S");

    # Make sure we got 6 violations: 3 mod, 1 add, 2 rm.
    #
    my ($n, $a, $r, $c) = twtools::AnalyzeReport( twtools::RunReport() );
    my $n_expected = $dir_mods ? 6 : 4;
    my $c_expected = $dir_mods ? 3 : 1;

    if( ($n != $n_expected) || ($a != 1) || ($r != 2) || ($c != $c_expected) )
    {
        twtools::logStatus("IC with FS section failed: $n $a $r $c\n");
        $twpassed = 0;
    }

    #######################################################
    # Now just run RuleA
    #
    RemoveFile("$reportloc");
    twtools::RunIntegrityCheck({trailing_opts => "-R RuleA"});

    # Make sure we got 4 violations this time: 2 mod, 1 add, 1 rm.
    #
    my ($n, $a, $r, $c) = twtools::AnalyzeReport( twtools::RunReport() );
    my $n_expected = $dir_mods ? 4 : 3;
    my $c_expected = $dir_mods ? 2 : 1;

    if( ($n != $n_expected) || ($a != 1) || ($r != 1) || ($c != $c_expected) )
    {
        twtools::logStatus("IC of Rule A failed: $n $a $r $c\n");
        $twpassed = 0;
    }

    #######################################################
    # Now run severity level 200, meaning RuleB
    #
    RemoveFile("$reportloc");
    twtools::RunIntegrityCheck({trailing_opts => "-l 200"});

    # Make sure we got 2 violations this time: 1 mod, 0 add, 1 rm.
    #
    my ($n, $a, $r, $c) = twtools::AnalyzeReport( twtools::RunReport() );
    my $n_expected = $dir_mods ? 2 : 1;
    my $c_expected = $dir_mods ? 1 : 0;

    if( ($n != $n_expected) || ($a != 0) || ($r != 1) || ($c != $c_expected) )
    {
        twtools::logStatus("IC of severity 200+ failed: $n $a $r $c\n");
        $twpassed = 0;
    }

    #######################################################
    # Now run severity level "high", also meaning RuleB
    #
    RemoveFile("$reportloc");
    twtools::RunIntegrityCheck({trailing_opts => "-l high"});

    # Make sure we got 2 violations this time: 1 mod, 0 add, 1 rm.
    #
    my ($n, $a, $r, $c) = twtools::AnalyzeReport( twtools::RunReport() );
    my $n_expected = $dir_mods ? 2 : 1;
    my $c_expected = $dir_mods ? 1 : 0;

    if( ($n != $n_expected) || ($a != 0) || ($r != 1) || ($c != $c_expected) )
    {
        twtools::logStatus("IC of severity 'high' failed: $n $a $r $c\n");
        $twpassed = 0;
    }

    #######################################################
    # Now run against one object, modify.txt
    #
    RemoveFile("$reportloc");
    twtools::RunIntegrityCheck({trailing_opts => "$root/subdir/modify.txt"});

    # Make sure we got 1 violation this time: 1 mod, 0 add, 0 rm.
    #
    my ($n, $a, $r, $c) = twtools::AnalyzeReport( twtools::RunReport() );

    if( ($n != 1) || ($a != 0) || ($r != 0) || ($c != 1) )
    {
        twtools::logStatus("IC of single object modify.txt failed: $n $a $r $c\n");
        $twpassed = 0;
    }

    #######################################################
    # Run a few full check w/ email reporting, all the valid levels
    #  (we're configured to pipe to cat as a fake mailprogram)
    #
    RemoveFile("$reportloc");
    twtools::RunIntegrityCheck({trailing_opts => "-M -t 0"});
    twtools::RunIntegrityCheck({trailing_opts => "-M -t 1"});
    twtools::RunIntegrityCheck({trailing_opts => "-M -t 2"});
    twtools::RunIntegrityCheck({trailing_opts => "-M -t 3"});
    twtools::RunIntegrityCheck({trailing_opts => "-M -t 4"});

    # Make sure we got 6 violations: 3 mod, 1 add, 2 rm.
    #
    my ($n, $a, $r, $c) = twtools::AnalyzeReport( twtools::RunReport() );
    my $n_expected = $dir_mods ? 6 : 4;
    my $c_expected = $dir_mods ? 3 : 1;

    if( ($n != $n_expected) || ($a != 1) || ($r != 2) || ($c != $c_expected) )
    {
        twtools::logStatus("Full IC failed: $n $a $r $c\n");
        $twpassed = 0;
    }


    #######################################################
    # Now run an interactive IC with cat as a fake editor, so DB gets auto updated.
    #
    RemoveFile("$reportloc");
    twtools::RunIntegrityCheck({trailing_opts => "-I -V cat -P $twtools::twlocalpass"});

    # Make sure we got 6 violations: 3 mod, 1 add, 2 rm.
    #
    my ($n, $a, $r, $c) = twtools::AnalyzeReport( twtools::RunReport() );
    my $n_expected = $dir_mods ? 6 : 4;
    my $c_expected = $dir_mods ? 3 : 1;

    if( ($n != $n_expected) || ($a != 1) || ($r != 2) || ($c != $c_expected) )
    {
        twtools::logStatus("Interactive IC failed: $n $a $r $c\n");
        $twpassed = 0;
    }

    #######################################################
    # Finally run another full IC to verify db was updated
    #  + also exercise the verbose & hex output options since we don't elsewhere.
    #
    RemoveFile("$reportloc"); 
    twtools::RunIntegrityCheck({trailing_opts => "-v -h"});

    # Make sure we got no violations this time
    #
    my ($n, $a, $r, $c) = twtools::AnalyzeReport( twtools::RunReport() );

    if( ($n != 0) || ($a != 0) || ($r != 0) || ($c != 0) )
    {
        twtools::logStatus("IC after interactive IC failed: $n $a $r $c\n");
        $twpassed = 0;
    }

    #########################################################
    #
    # See if the tests all succeeded...
    #
    if ($twpassed) {
        ++$twtools::twpassedtests;
        print "PASSED\n";
        return 1;
    }
    else {
        ++$twtools::twfailedtests;
        print "*FAILED*\n";
        return 0;
    }
}



######################################################################
#
# Initialize the test
#

sub initialize 
{
    # Make the policy file
    #
    twtools::GeneratePolicyFile( PolicyFileString() );
    return 1;
}


sub cleanup
{
	# remove all of the files we were integrity checking...
	#
	system( "rm -rf $root/*" );
	system( "rm -rf $root2/*" );
	$? && print "WARNING: IC cleanup failed.\n";

	# remove the report files we created...
	#
	system( "rm -f $report1" ) if (-e $report1);
	system( "rm -r $report2" ) if (-e $report2);
	system( "rm -r $report3" ) if (-e $report3);
	system( "rm -r $report4" ) if (-e $report4);

}


######################################################################
# One time module cleanup goes in here...
#
END 
{
}

1;

