#!/usr/bin/perl -w
# $Id$
# This script (sophomorix-virusscan) is maintained by Rüdiger Beck
# It is Free Software (License GPLv3)
# If you find errors, contact the author
# jeffbeck@web.de  or  jeffbeck@gmx.de

# todo:

# make sure sophomorix-check runs, when virusscan is runnung ???
#
# was ist mit schultausch?, lehrertausch
#
# optionen
# --max-time minutes
# Versuche die --max-time nicht zu überschreiten

# Szenario der Eskalation
# Wenn Virus gefunden, dann
# A) 
#    1) Mail an user
#    2) Tauschverzeichnisse auf die er Zugriff hatte mitscannen
#    3) Mail????, was noch

# mail wenn user aus der penalty db entlassen wird:
# 5mal gescannt, nix gefunden, ....bitte weiter aufpassen
#
# user beim setzten auf 0 in der penalty db belassen
# zusätzlicher Zähler der anzeigt wie oft schon viren gefunden wurden

# was ist, wenn pfad zum virus '::' enthält

# ownership in die logdateien schreiben



# ????? weiter:
# --info (zeige absoluter pfad bei scanobjects)
#
# # an penalty db soll immer was veraendert werden, schritt 1,2,3
# -> checken
# # wenn auto last zum ende gleich ist wie zu beginn:
# oder counter fuer scanned objects 
# -> fehlermeldung (mail)
#
# option --loginfo --> uerberblick
#
# testen: wird IMMER die penalty db upgedated?
#         - nein nicht bei --test
#         - ja bei auto scanned user 
#
# testen: aus penalty db entfernt?
#         - ja bei lehrer
#
# todo: 
# ? abs_pfad mit ausgeben bei option --info


# Hallo Herr/Frau xxx,

# in ihrem Home-Verzeichnis am BSZ Leonberg wurde ein Virus gefunden 
# und entfernt:

# /home/teachers/ke/_auszuteilen/Wechseldatenträger (E)/RavMon.exe: Trojan.Agent-1914 FOUND
# /home/teachers/ke/_auszuteilen/Wechseldatenträger (E)/RavMon.exe: moved to '/var/log/clamav/quarantine/RavMon.exe'


# Falls die entfernte Datei wider erwarten von ihnen gebraucht wird, 
# melden sie sich bitte.

# Bitte überprüfen sie auch ihren PC zu Hause und ihre USB Sticks nach Viren.



# Viele Grüße, Rüdiger Beck



# Bibliotheken
use strict;
use Getopt::Long;
Getopt::Long::Configure ("bundling");
use Sophomorix::SophomorixConfig;
use Sophomorix::SophomorixBase;
use Sophomorix::SophomorixAPI;
use DBI;
use Net::LDAP;
use Term::ANSIColor;

use Sophomorix::SophomorixPgLdap qw(show_modulename
                                    check_connections
                                    fetchdata_from_account
                                    fetchusers_from_adminclass
                                   );


my @arguments = @ARGV;


# ===========================================================================
# Variablen
# ==========================================================================

my $help=0;
my $info=0;
# penalty for a virus, default=3
my $penalty=3;
my $skip_conf=0;
my $skip_penalty=0;
my $skip_auto=0;
my $skip_scanning=0;
my $scan_object="";
my $copy=0;
my $max_start_period=1; # in minutes

# counter for scanned objects
my $scanned_objects_counter=0;

# penalty database
my $penalty_db_dir="/var/lib/sophomorix/virusscan";
my $penalty_db=$penalty_db_dir."/penalty.db";;
system("/bin/mkdir -p $penalty_db_dir");
system("/usr/bin/touch $penalty_db");
my %penalty=&read_penalty_db_hash;

# abs path of scanned dirs/files
my %scanned_objects=();

my $logfile="/var/log/clamav/sophomorix-virusscan.log";
my $log_last_auto="/var/log/clamav/sophomorix-virusscan.last-auto-scanned";
my $log_clam="/var/log/clamav/sophomorix-virusscan.clamav.log";
my $quarantine="/var/log/clamav/quarantine";
system("/bin/mkdir -p $quarantine");

my $last_auto_scanned=&get_last_auto_scanned();

#my $config_dir="/etc/sophomorix/virusscan";
my $config_file="sophomorix-virusscan.conf";
my $config=$DevelConf::virusscan_conf."/".$config_file;
my $exclude_file="sophomorix-virusscan-excludes.conf";
my $excludes=$DevelConf::virusscan_conf."/".$exclude_file;

# list of all usable number entries in sophomorix-virusscan.conf
my %convert_days = qw(
     1     01          01    01
     2     02          02    02
     3     03          03    03
     4     04          04    04
     5     05          05    05
     6     06          06    06
     7     07          07    07
     8     08          08    08
     9     09          09    09
     10    10          11    11
     12    12          13    13
     14    14          15    15
     16    16          17    17
     18    18          19    19
     20    20          21    21
     22    22          23    23
     24    24          25    25
     26    26          27    27
     28    28          29    29
     30    30          31    31
);


# list of all usable string entries in sophomorix-virusscan.conf
my %convert_days_of_week = qw(
     monday        1 
     mondays       1 
     tuesday       2 
     tuesdays      2 
     wednesday     3 
     wednesdays    3 
     thursday      4 
     thursdays     4 
     friday        5
     fridays       5
     saturday      6
     saturdays     6
     sunday        7 
     sundays       7 
);


my %excludes=&get_excludes();


open(LOG, ">>$logfile");

if (not -x "/usr/bin/clamscan"){
    print "\nERROR: clamscan binary not found!",
          " (package clamav not installed?)\n\n";
    exit;
}


# ===========================================================================
# Optionen verarbeiten
# ==========================================================================


# Parsen der Optionen
my $testopt=GetOptions(
           "help|h" => \$help,
           "info|i" => \$info,
           "copy" => \$copy,
           "skip-conf" => \$skip_conf,
           "skip-penalty" => \$skip_penalty,
           "skip-auto" => \$skip_auto,
           "skip-scanning" => \$skip_scanning,
           "test|scan-object=s" => \$scan_object,
           "max-start-period|msp=i" => \$max_start_period,
           "penalty=i" => \$penalty,
          );

# Prüfen, ob Optionen erkannt wurden, sonst Abbruch
&check_options($testopt);
&check_connections();


# ===========================================================================
# Help
# ==========================================================================

# --help
if ($help==1) {
   # Scriptname ermitteln
   my @list = split(/\//,$0);
   my $scriptname = pop @list;
   # Befehlsbeschreibung
   print('
sophomorix-virusscan scans scan-objects (userhomes,shares,directories,files) for viruses

A scan-object can be:
  - a username                 --> scans $HOME of that user
  - a class,subclass,project   --> scans the share of class,subclass,project
  - an absolute path           --> scans the file/directory

Options:
  -h  / --help
  -v  / --verbose
  -vv / --verbose --verbose
  -i  / --info  (show what objects will be scanned next)
  --copy (copy viruses to quarantine, instead of moving)
  --test scan-object (Test performance of scan)
  --max-start-period (in minutes (default: 1 minute), 
                   start scanning a new object, if max-start-time is not over)
  --penalty number (penalty for finding a virus, default is 3 points)

Options that skip scanning:
  --skip-conf (Do not scan scan-objects in sophomorix-virusscan.conf)
  --skip-penalty (Do not scan scan-objects in the penalty database)
  --skip-auto (Do not scan scan-objects automatically (=alphabetically))
  --skip-scanning (Do not scan ANY scan-objects, but run the script)


Without any options, sophomorix-virusscan scans as configured and does not 
start a scan after 1 minute.


Scan Order:

1) Scanning using data from sophomorix-virusscan.conf:
   Configure here which scan-object to scan on which days of the month.
   Use this for unreliable users, that must be scanned regularly.

2) Scanning penalty objects:
   sophomorix-virusscan manages a database with objects that were infected. 
   It scans a scan-object again if it had a virus. 

3) Scanning automatically:
   Scans scan-objects in alphabetical order. 
   When option --max-start-period (in minutes, default 1 minute) is given,
   avoid START scanning after this period. 

Files maintained by sophomorix-virusscan:

');

print "Configuration: $config\n"; 
print "Quarantine:    $quarantine\n"; 
print "Logfile:       $logfile\n";
print "Clamav:        $log_clam\n";
print "Penalty DB:    $penalty_db\n";
print "Last autoscan: $log_last_auto\n";

   print('
Please see the sophomorix-virusscan(8) man pages for full documentation
');
   print "\n";
   exit;
}

#&log_script_start(@arguments);

# Start date in unix seconds
my $start_time=time;
my $max_start_time=$start_time+60*$max_start_period;
my ($start_time_human)=&time;
my $day_of_month=`/bin/date +%d`;
chomp($day_of_month);
my $day_of_week=`/bin/date +%u`;
chomp($day_of_week);
# message to sent to root and add to the end of the Script
my $root_mail="Scan results on $start_time_human:\n";



# ===========================================================================
# Info
# ==========================================================================

# --info
if ($info==1){
    print "\nToday is ";
    system("/bin/date");
    print "\n";

    print "1) Scanning using data from $config_file\n";
    open(CONF, "<$config") || die "\nERROR: $config does not exist!\n\n";
    while(<CONF>){
        if (/^#/ or /^\s*$/){
            next;
        }
        # split line
        chomp();
        my ($object,$string) = split(/::/);
        my @options=split(/,/,$string);
        my $red=0;
        foreach my $option (@options){
            if (exists $convert_days_of_week{$option}){
                # check if option is a known string (monday, ...)
                if ($day_of_week eq $convert_days_of_week{$option}){
                    # remember to show line in red
                    $red=1;
                }
            } elsif (exists $convert_days{$option}) {
                # check if option is a known number
                # add leading 0 if necessary
                $option=$convert_days{$option};
                if ($day_of_month eq $option){
                    # remember to show line in red
                    $red=1;
                }
	    } else {
                # unknown option
                print "\nERROR: $option is not an allowed option",
                      " in $config_file\n\n";
                exit;
            }
        }

        # show line in red, if necessary 
        if ($red==1){
  	    print color 'bold red';
            print "   $_\n";
	    print color 'reset';
        } else {
            print "   $_\n";
        }
    }
    close(CONF);

    print "\n";

    print "2) Scanning penalty objects\n";
    open(PENALTY, "<$penalty_db");
    while(<PENALTY>){
        if (/^#/){
            next;
        }
  	print color 'bold red';
        print "   $_";
	print color 'reset';
    }
    close(PENALTY);

    print "\n";

    print "3) Scanning automatically\n";
    #print "   $last_auto_scanned\n";
    my @autoscan_object_list = &fetch_autoscan_objects();
    my $continue=0;
    my $show_num_objects=5;
    foreach my $object (@autoscan_object_list){
        if ($last_auto_scanned eq "" and $continue==0){
    	    print color 'bold red';
            print "   $object\n";
            $continue++;
            $continue++;
        } elsif ($continue > 0){
    	    print color 'bold red';
            print "   $object\n";
            $continue++;
            if ($continue > $show_num_objects){ # show
                print "   ... \n";
	        print color 'reset';
                last;
	    } else {
                next;
            }
        } elsif ($object eq $last_auto_scanned){
            print "   $last_auto_scanned\n";
            $continue=1; # remember to continue next time
            next;
        }       
    }

    print "\nNext scan objects are in ";
    print color 'bold red';
    print "red color";
    print color 'reset';

    print ".\n\n";
    exit;
}




# --test|--scan-object
if ($scan_object ne ""){
    &scan_object($scan_object,"test");
    &exit_script;
}


# ===========================================================================
# 1) Scanning using data from sophomorix-virusscan.conf
# ==========================================================================
print "1) Scanning using data from $config_file\n";
open(CONF, "<$config");
while(<CONF>){
    if (/^#/ or /^\s*$/){
        next;
    }
    my ($object,$options) = split(/::/);
    my (@options) = split(/,/,$options);

    print "   Scan-Object: $object\n";
    foreach my $option (@options){
        if (exists $convert_days_of_week{$option}){
            # check if $option is a known string (monday, tuesday, ...)
            if ($day_of_week eq $convert_days_of_week{$option}){
                print "   # On ${option} $object is scanned:\n";
                if ($skip_conf==0){
                    &scan_object($object,"config");
	        } else {
                    print "     * skipping $object (Option --skip-conf)\n";
                }
            } else {
                print "     $object will be scanned on ${option}, NOT today!\n";
            }
        } elsif (exists $convert_days{$option}){
            # check if option is a known number
            if ($option==$day_of_month){
                print "   # On the ${option}. day of the month,",
                      " $object is scanned:\n";
                if ($skip_conf==0){
                    &scan_object($object,"config");
	        } else {
                    print "     * skipping $object (Option --skip-conf)\n";
                }
            } else {      
                print "     $object will be scanned on the ${option}. day ",
                      "of the month, NOT today!\n";
            }
        } else {
            # unknown option
            print "\nERROR: $option is not an allowed option",
                  " in $config_file\n\n";
            exit;
        }
    }


}
close(CONF);

# ===========================================================================
# 2) Scanning penalty objects
# ==========================================================================
print "2) Scanning penalty objects\n";
my @penalty_db_list = &read_penalty_db_list();
foreach my $object (@penalty_db_list){
    if ($skip_penalty==0){
        &scan_object($object,"penalty");
    }
}


# ===========================================================================
# 3) Scanning automatically
# ==========================================================================
print "3) Scanning automatically\n";
my @autoscan_object_list = &fetch_autoscan_objects();
my $last_of_autoscan_object_list = $autoscan_object_list[ -1 ];
my $continue=0;
# was auto last found or not?
my $auto_last_found=0;
my $auto_end_reached=0;
foreach my $object (@autoscan_object_list){
    if ($last_auto_scanned eq ""){
        $auto_last_found=1;
        # continue, if no last scanned object exists
    } elsif ($object eq $last_auto_scanned){
        $auto_last_found=1;
        # last auto scanned object reached, skip it
        $continue=1; # remember to continue next time
        next;
    } elsif ($continue==1) {
        # continue
    } else {
	next;
    }
    # scan now
    if ($skip_auto==0){
        &scan_object($object,"auto");
        # check if it was the last object in the list
        if ($object eq $last_of_autoscan_object_list){
            $auto_end_reached=1;
        }
    } else {
        print "     * skipping $object (Option --skip-auto)\n";
    }
}


if ($auto_last_found==0){
    # auto last not found (user deleted, share removed, ...)
    # --> set empty auto last
    &write_last_auto_scanned("");
    print "Could not find last auto-scanned scan-object\n";
    print "Setting last auto-scanned scan-object to empty string\n";
}


if ($auto_end_reached==1){
    # last object reached
    # --> set empty auto last
    &write_last_auto_scanned("");
    print "Final autoscan-object was scanned\n";
    print "Setting last auto-scanned scan-object to empty string\n";
    print "This will continue scanning on first scan-object in list\n";
}


&write_penalty_db;
&exit_script;






############################################################
# subs
############################################################

sub get_last_auto_scanned {
    my $last="";
    if (not -e $log_last_auto){
        # file does not exist -> empty field
        return "";
    }
    open(LAST, "<$log_last_auto");
    while(<LAST>){
        $last=$_;
    }
    chomp($last);
    close(LAST);
    return $last;
}



sub write_last_auto_scanned {
    my ($last) = @_;
    open(LAST, ">$log_last_auto");
    print LAST "$last\n";
    close(LAST);
}



sub fetch_autoscan_objects {
    my @objects=();
    # teachers and students
    my @teachers=&fetchusers_from_adminclass(${DevelConf::teacher});
    my @students=&fetchstudents_from_school();
    @objects = (@teachers,@students);

    # shares
    my @dirlist=($DevelConf::share_classes,
                 $DevelConf::share_subclasses,
                 $DevelConf::share_projects);

    foreach my $share (@dirlist){
        opendir SHARE, $share or return;
        foreach my $object (readdir SHARE){
              if ($object eq "."){next};
              if ($object eq ".."){next};
              push @objects, $object;
        }
        closedir SHARE;
    }

    # sort
    @objects = sort @objects;

    # print
    #foreach my $object (@objects){
    #    print "$object\n";
    #}
    return @objects;
}



sub exit_script {
    # End of script
    print "\nSummary:\n";
    print "   Data logged into file $logfile\n";
    print "   Data logged into file $log_clam\n";
    print "   Penalty database is in $penalty_db\n";
    print "   $scanned_objects_counter objects scanned\n";
    print "$root_mail";
    close(LOG);
    exit;
}



sub scan_object {
    my ($scan_object,$option) = @_;
    # options can be: 
    # test
    # config
    # penalty
    # auto
    my $exclude_string="";
    my ($scan_dir,$group) = &fetchdata_from_account($scan_object);
    if ($scan_dir eq ""){
        # its not a users home
        if (-e "$DevelConf::share_classes/$scan_object"){
            $scan_dir="$DevelConf::share_classes/$scan_object";
            print "   # $scan_object is a class: $scan_dir\n";
	} elsif (-d "$DevelConf::share_subclasses/$scan_object"){
            $scan_dir="$DevelConf::share_subclasses/$scan_object";
            print "   # $scan_object is a subclass: $scan_dir\n";
	} elsif (-d "$DevelConf::share_projects/$scan_object"){
            $scan_dir="$DevelConf::share_projects/$scan_object";
            print "   # $scan_object is a project: $scan_dir\n";
	} elsif (-d "$DevelConf::share_projects/p_$scan_object"){
            $scan_dir="$DevelConf::share_projects/p_$scan_object";
            print "   # $scan_object is a project: $scan_dir\n";
	} elsif (-d "$DevelConf::share_share/$scan_object"){
            $scan_dir="$DevelConf::share_share/$scan_object";
            print "   # $scan_object is the school share/parent",
                  " share: $scan_dir\n";
	} elsif (-d "$scan_object"){
            print "   # $scan_object is an absolute path to a directory\n";
            $scan_dir="$scan_object";
	} elsif (-f "$scan_object"){
            print "   # $scan_object is an absolute path to a file\n";
            $scan_dir="$scan_object";
	} else {
            $scan_dir="$scan_object";
        }
    } else {
        # its a user
        print "   # $scan_object is a users home ($group): $scan_dir\n";
    }

    if ($skip_scanning==1){
        print "     * skipping $scan_object ($scan_dir)\n";
        return 2;
    }

    # stop scanning if it is too late
    my $time_now=time;
    if ($time_now>$max_start_time){
        my $overtime=$time_now-$max_start_time;
        print "   * too late ($overtime seconds) ",
              "for $scan_object ($scan_dir)\n";
        &write_penalty_db;
        &exit_script;
    }

    print LOG "\n\n##############################",
              "##################################\n";
    printf LOG "####### %-26s: %20s #######\n",
               $scan_object,
               $start_time_human;
    printf LOG "####### %-48s #######\n",$scan_dir;
    #print LOG "   * start time is $start_time\n";

    my $result="";
    my $infected=-1;
    my @viruses=();
    my $performance="";

    if  (-e $scan_dir){
        # check against double scanning
        if (exists $scanned_objects{$scan_dir}){
            # no scanning
            $result="\nERROR: $scan_dir was scanned today already,".
                    " skipping\n\n";
            $infected=-2;
            $performance="";
            print LOG "$result\n";
            printf LOG "####### %-28s: %8s kB/second #######\n",
                       $scan_object,$performance;
            printf LOG "####### %-28s: %8s viruses   #######\n",
                       $scan_object,$infected;
            print "     --> $scan_object scanned already\n";
            return 0;
        }

        print "   # Excludes in $scan_dir:\n";
        print LOG "Excludes in $scan_dir:\n";
        # create exclude string, if necessary
        if (exists $excludes{$scan_dir}){
	    foreach my $item ( keys %excludes ) {
                if ($item eq $scan_dir){ 
		    foreach my $i ( 0 .. $#{ $excludes{$item}} ) {
                        $exclude_string=$exclude_string.
                                        " ".
                                        $excludes{$item}[$i];
                    print LOG "  $excludes{$item}[$i]\n";
                    print "     $excludes{$item}[$i]\n";
                    }
	        }
            } 
        } else {
            print LOG "  * no Excludes in $excludes\n";
            print "      * no Excludes in $excludes\n";
        }

        # scanning
        # clamav geht keinen links nach
        # amount of data to scan
        my $data_kb=`/usr/bin/du -s $scan_dir`;
        # kB or kiB???????
        ($data_kb)=split(/\s+/,$data_kb);
        # calculation MB for printout
        my $data_mb=$data_kb/1024;
        $data_mb=int($data_mb);
        printf LOG "####### Predicted size of Scan Object: %14s MB #######\n",
                    $data_mb;

        print "   # Scanning ...\n";
        if ($copy==1){
           $result=`/usr/bin/clamscan -r -i -l $log_clam $exclude_string --copy=$quarantine $scan_dir`;
        } else {
           # copy also, is removed later
           $result=`/usr/bin/clamscan -r -i -l $log_clam $exclude_string --copy=$quarantine $scan_dir`;
        }
        $scanned_objects_counter++;
        #my $command="clamscan -v -r -i -l $log_clam --move=$quarantine ".
        #            "$scan_dir >> $logfile 2>&1";
        # analyze result
        ($infected) = &get_infected($result);
        @viruses = &get_virus_list($result);
        my $now=time;
        my $scan_time=$now-$start_time;
        if ($scan_time==0){
            $scan_time=1;
        }
        $performance=$data_kb/$scan_time;
        $performance=int($performance);
        
        # remember scanned dir/file
        $scanned_objects{$scan_dir}="scanned";

    } else {
        # no scanning
        $result="\nERROR: $scan_dir is nonexistent\n\n";
    }

    # update penalty database
    if ($infected > 0){
        my $old=0;
        if (exists $penalty{$scan_dir} ){
            $old=$penalty{$scan_dir};
        }
        my $new = $old + $penalty;
        $penalty{$scan_dir}="$new";
    } elsif ($infected == 0){
        if (exists $penalty{$scan_dir} ){
            my $old=$penalty{$scan_dir};
            my $new = $old - 1;
            $penalty{$scan_dir}="$new";
        }
    }

    print LOG "$result\n";
    printf LOG "####### %-28s: %8s kB/second #######\n",
               $scan_object,$performance;
    printf LOG "####### %-28s: %8s viruses   #######\n",
               $scan_object,$infected;
    $root_mail=$root_mail."   ".$infected." viruses in ".$scan_object."\n";
    print "     --> $infected viruses in $scan_object\n";

    my $vir_count=1;
    foreach my $virus (@viruses){
        my ($found,$quarantine,$owner,$group)=split(/::/,$virus);
        $root_mail=$root_mail."     $vir_count) Found at:  ".$found."\n";
        # get info from virus file
        my $uid = (stat $found)[4];
        my $gid = (stat $found)[5];
        my $virus_owner = (getpwuid $uid)[0];
        my $virus_group = (getgrgid $gid)[0];
        #print "Virusowner is $virus_owner\n";
        #print "Virusgroup is $virus_group\n";
        my ($virus_mtime_epoch,$virus_ctime_epoch) = (stat($found))[9,10];

        my $virus_mtime=localtime($virus_mtime_epoch);
        $virus_mtime=~s/ /-/g;
        #print "mtime is $virus_mtime\n";

        my $virus_ctime=localtime($virus_ctime_epoch);
        $virus_ctime=~s/ /-/g;
        #print "ctime is $virus_ctime\n";
        

        my $chown_command="chown ${virus_owner}:${virus_group} \"$quarantine\"";
        system($chown_command);

        # set mtime,ctime

        my $metadata_filename=$quarantine.
                              "_ctime=".$virus_ctime.
                              "_mtime=".$virus_mtime;
        system("touch \"$metadata_filename\"");

        if ($copy==1){
            $root_mail=$root_mail."        Copied to: ".$quarantine."\n";
        } else {
            system("rm \"$found\"");
            $root_mail=$root_mail."        Moved to:  ".$quarantine."\n";
        }
        $root_mail=$root_mail."           Owner:        ".$virus_owner."\n";
        $root_mail=$root_mail."           Group:        ".$virus_group."\n";
        $root_mail=$root_mail."           Creation:     ".$virus_ctime."\n";
        $root_mail=$root_mail."           Modification: ".$virus_mtime."\n";
        $vir_count++;
    }


    if ($option eq "test"){
        # nothing
    }
    if ($option eq "config"){
        # nothing
    }
    if ($option eq "penalty"){
        # nothing
    }
    if ($option eq "auto"){
        &write_last_auto_scanned($scan_object);
    }
    return 1;
}



sub time {
    my $date = `date +%Y-%m-%d_%H-%M-%S`;
    chomp($date);
    my ($year,$month,$day,$hour,$minute,$second)=split(/[-_:]/,$date);
    my $human=$year."-".$month."-".$day." at ".$hour.":".$minute;
    return ($human,$year,$month,$day,$hour,$minute,$second);
}



sub get_infected {
    my ($result) = @_;
    my $text;
    my $infected="";
    my @lines = split(/\n/,$result);
    foreach my $line (@lines){
        if ($line=~m/Infected files:/){
            ($text,$infected) = split(/:/, $line);
            $infected=~s/ //g;
            #print "Infect: ---$infected---\n";
        }
        #print LOG "HE: $line\n";
    }
    if ($infected eq ""){
        print "\nERROR: Could not retrieve number of infected files\n\n";
    }
    return $infected;
}



sub get_virus_list {
    # returns a list: 
    # /path/of/virus::/path/of/virus/in/quaratine::owner::
    my ($result) = @_;
    my @viruses=();
    my @lines = split(/\n/,$result);

    # save virusdata 
    my $virus_path="";
    my $message="";
    my $virus_quarantine_path="";
    my $found_first_line=0; # if 1, then search for second line

    foreach my $line (@lines){
        # search for the first line
        if ($line=~m/FOUND/){
            $found_first_line=1;
            ($virus_path,$message) = split(/:/, $line);
            # remove leading space
            $message=~s/^\s//g;
            next; 
        }
        
        # search for the second line 
        if ($line=~m/$virus_path/ and $found_first_line==1){
	    my $target;
            my $virus_path_2;
            if ($copy==1){
                ($virus_path_2,$target) = split(/copied to/, $line);
            } else {
                #($virus_path_2,$target) = split(/moved to/, $line);
                # moving is done by copying and deleting
                ($virus_path_2,$target) = split(/copied to/, $line);
            }
            if ( $target =~m/^\s'(.*)'$/ ) {
                # find quaratined file
                $virus_quarantine_path=$1;

                # find out owner/gowner of $virus_quarantine_path
                my @statlist = stat($virus_quarantine_path);
                my $owner = "unknown";
                my $group = "unknown";
                if (defined $statlist[4]){ 
                    $owner = getpwuid $statlist[4];
                }
                if (defined $statlist[5]){ 
                    $group = getgrgid $statlist[5];
                }

                # save data
                my $virus=$virus_path."::".
                          $virus_quarantine_path."::".
                          $owner."::".
                          $group."::";
                push @viruses, $virus;

                # reset values
                $found_first_line=0;
                $virus_path="";
                $message="";
                $virus_quarantine_path="";
            }
        }
        #print LOG "HE: $line\n";
    }
    return @viruses;
}



sub read_penalty_db_hash {
    my %penalty=();
    my $penalty=0;
    open(PENALTY, "<$penalty_db");
    while(<PENALTY>){
        # skip comments
        if (/^#/){
            next;
        }
        my ($object,$penalty) = split(/::/);
        $penalty{$object}="$penalty";
    }
    close(PENALTY);
    return %penalty;
}



sub read_penalty_db_list {
    my @penalty=();
    my $penalty=0;
    open(PENALTY, "<$penalty_db");
    while(<PENALTY>){
        # skip comments
        if (/^#/){
            next;
        }
        my ($object,$penalty) = split(/::/);
        #$penalty{$object}="$penalty";
        push @penalty, $object;
    }
    close(PENALTY);
    return @penalty;
}



sub write_penalty_db {
    # write all penalties >0
    open(PENALTY, ">$penalty_db");
    print PENALTY "# penalty\n";
    while (my ($scan_object,$penalty) = each %penalty){
        my $line = $scan_object."::".$penalty."::\n";
        if ($penalty > 0){
            print PENALTY $line;
        }
    }
    close(PENALTY);
}


sub get_excludes {
    my %excludes=();
    print "* Reading $excludes\n";
    open(EXCLUDE, "<$excludes") || die "\nERROR: $excludes does not exist!\n\n";
    while(<EXCLUDE>){
        # skip comments
        if (/^#/ or /^\s*$/){
            next;
        }
        chomp();
        my ($path,$excl) = split(/::/);
	push @{ $excludes{$path} }, $excl; 
    }
    close(EXCLUDE);
    return %excludes;
}
