#!/usr/bin/perl -w
# osec_reporter
#
# This file is part of Osec (lightweight integrity checker)
# Copyright (c) 2002-2007  by Stanislav Ievlev
# Copyright (c) 2008-2009  by Alexey Gladkov
#
# This file is covered by the GNU General Public License,
# which should be included with osec as the file COPYING.
#

use strict;

my %new_normal_files=();
my %del_normal_files=();
my %change_normal_files=();

my %new_bad_files=();
my %del_bad_files=();
my %info_bad_files=();
my %change_bad_files=();

my %changed_symlinks = ();
my %symlinks=();

my $process = 1;

while(<STDIN>) 
{

    my @fields = split /\t+/;
#    s/^\s*(\S+.*?)\s*$/$1/ foreach (@fields); #trim

    if (/^Init\s+(.*?)\.\.\.$/)
    {
	print;
	$process = 0;
	next;
    }

    if (/^Processing\s+(.*?)\.\.\.$/)
    {
	print;
	$process = 1;
	next;
    }
    
    my $first_bad  = undef; #first possible bad comment
    my $second_bad = undef; #second possible bad comment

    #always save bad file information if we have it
    if ($fields[3] and $fields[3] =~ m/.*\[(.*)\]$/)
    {
	$first_bad = $1;
	$first_bad =~ s/^\s*(\S+.*?)\s*$/$1/;
	$info_bad_files{$fields[0]}=$first_bad;
    }
    if ($fields[4] and $fields[4] =~ m/.*\[(.*)\]$/) #it's a new dangerous status
    {
	$second_bad = $1;
	$second_bad =~ s/^\s*(\S+.*?)\s*$/$1/;
	$info_bad_files{$fields[0]}=$second_bad;
    }

    #choose action
    if ($fields[1] eq "symlink" and $fields[2] and ($fields[2] eq "changed"))
    {
	$fields[3] =~ /^old\s+target=(.*)/ and $changed_symlinks{$fields[0]}{"old"}=$1;
	$fields[4] =~ /^new\s+target=(.*)/ and $changed_symlinks{$fields[0]}{"new"}=$1;
    }
    elsif ($fields[1] eq "stat" and $fields[2] and ($fields[2] eq "new"))
    {
	$process and $new_normal_files{$fields[0]}=1; #don't report about new files if we init database for this dir
	$first_bad and $new_bad_files{$fields[0]}=$first_bad;
    }
    elsif ($fields[1] eq "stat" and $fields[2] and ($fields[2] eq "removed"))
    {
	$del_normal_files{$fields[0]}=1;
	$first_bad and $del_bad_files{$fields[0]}=$first_bad;
    }
    elsif ($fields[1] eq "stat" and $fields[2] and ($fields[2] eq "changed"))
    {
	$fields[3] =~ s/^old\s+//;#remove 'old' prefix
	$fields[4] =~ s/^new\s+//;#remove 'new' prefix

	/(.*?)=(.*)/ and $change_normal_files{$fields[0]}{$1}{"old"}=$2 foreach (split / /,$fields[3]);
	/(.*?)=(.*)/ and $change_normal_files{$fields[0]}{$1}{"new"}=$2 foreach (split / /,$fields[4]);

	#also process bad file transformations
	if (not($first_bad) and $second_bad)
	{
		$new_bad_files{$fields[0]}=$second_bad;
	}
	elsif ($first_bad and not($second_bad))
	{
		$del_bad_files{$fields[0]}=$first_bad;
	}
	elsif ($first_bad and $second_bad)
	{
		my %old_bad_status = ();
		my %new_bad_status = ();

		foreach (split / /,$first_bad)
		{
		    if (/\s*(.*)=(.*)/)
		    {
			$old_bad_status{$1}=$_;
		    }
		    else
		    {
			$old_bad_status{$_}=$_;
		    }
		}
		foreach (split / /,$second_bad)
		{
		    if (/\s*(.*)=(.*)/)
		    {
			$new_bad_status{$1}=$_;
		    }
		    else
		    {
			$new_bad_status{$_}=$_;
		    }
		}

		my $out="";
		my @bad_fields=("suid","sgid","ww");
		foreach (@bad_fields)
		{
		    (not($old_bad_status{$_}) and $new_bad_status{$_}) and $out .= " +$new_bad_status{$_}";
		    ($old_bad_status{$_} and not($new_bad_status{$_})) and $out .= " -$old_bad_status{$_}";
		}
		if ($change_normal_files{$fields[0]}{"uid"} and $old_bad_status{"suid"} and $new_bad_status{"suid"})
		{
		    $out .=" suid($change_normal_files{$fields[0]}{uid}{old}->";
		    $out .="$change_normal_files{$fields[0]}{uid}{new})";
		}
		if ($change_normal_files{$fields[0]}{"gid"} and $old_bad_status{"sgid"} and $new_bad_status{"sgid"})
		{
		    $out .=" sgid($change_normal_files{$fields[0]}{gid}{old}->";
		    $out .="$change_normal_files{$fields[0]}{gid}{new})";
		}
		$change_bad_files{$fields[0]}=$out;
	}
    }
}

#additional check for checksum changes in bad files and check for files became symlinks
foreach (keys %change_normal_files)
{
    if($change_normal_files{$_}{"mode"} and $change_normal_files{$_}{"mode"}{"new"} =~ /^12/) {
	$symlinks{$_} = readlink($_);
	delete $change_normal_files{$_};
	next;
    }
    ($change_normal_files{$_}{"checksum"} and $info_bad_files{$_}) and $change_bad_files{$_} .=" checksum";
}

my $date = `LC_ALL=C LANG=C LANGUAGE=C date`;
chomp $date;
print "\nThis is a report generated by osec at '$date'\n\n";


#has any bad info
if (%new_bad_files or %del_bad_files or %change_bad_files)
{
     print "-- PLEASE PAY ATTENTION TO --\n";
     if (%new_bad_files)
     {
        print "New dangerous files :\n";
	print "\t- $_ is $new_bad_files{$_}\n" foreach (sort keys %new_bad_files);
     }

     if (%del_bad_files)
     {
        print "Removed from dangerous files list:\n";
	print "\t- $_ was $del_bad_files{$_}\n" foreach (sort keys %del_bad_files);
     }

     if (%change_bad_files)
     {
        print "Changed dangerous files:\n";
	print "\t- $_ [ $info_bad_files{$_} ]  $change_bad_files{$_}\n" foreach (sort keys %change_bad_files);
     }
     print "\n";
}

if (%symlinks)
{
    print "These regular files turned into symlinks:\n";
    print "\t- $_ --> $symlinks{$_}\n" foreach (sort keys %symlinks);
    print "\n";
}

if (%changed_symlinks)
{
    print "These symlinks changed their target:\n";
    print "\t- $_ -> '$changed_symlinks{$_}{'new'}', was '$changed_symlinks{$_}{'old'}'\n" foreach (sort keys %changed_symlinks);
    print "\n";
}

if (%new_normal_files)
{
    print "New files added to control:\n";
    print "\t- $_\n" foreach (sort keys %new_normal_files);
}

if (%del_normal_files)
{
    print "Removed from control:\n";
    print "\t- $_\n" foreach (sort keys %del_normal_files);
}

if (%change_normal_files)
{
    print "Changed controlled files:\n";
    foreach (sort keys %change_normal_files)
    {
	print "\t- $_\n";
	my %item=%{$change_normal_files{$_}};
	foreach (keys %item) # print additional info except of checksum
	{
	    if ("$_" eq "mtime") {
		    $item{$_}{old} = localtime($item{$_}{old});
		    $item{$_}{new} = localtime($item{$_}{new});
	    }
	    print "\t\t$_: $item{$_}{old} -> $item{$_}{new}\n" unless ($_ eq "checksum")
	} 
    }
}

if (not(%new_normal_files or %del_normal_files or %change_normal_files or
        %new_bad_files or %del_bad_files or %change_bad_files or
        %symlinks or %changed_symlinks))
{
    print "No changes\n";
}

print "\n";
