#!/usr/local/bin/perl -w

#
# This script is part of the WatchFrog package. This script 
# was installed on %%DATE%% by %%USER%%.
#
# This script was written to perform security checks on files
# and directories. It will take advantage of the extended
# set of CheckSum implementation of the chksum in order to
# periodically monitor files declared in the 
# /usr/local/etc/watchfrog.conf
# Note that this file is subject to checksum test itself.
#
# Configuration is on top of the script along with extraneous
# comments.
#
# Why WatchFrog ??
#
# Just a joke. There are software called Tiger, WatchDogs .. so
# why not WatchFrog (see it as leaps in checksums)
#
# Written by J.Lauret Sep 5 2000
#
# Sep  6 2000 improved/done :
#  - when the configuration file is changed, warn $USER and do NOT
#    trust it. If deleted, warn users as well.
#  - check the .lis modifications as well. if changed, consider it
#    as not trust-worthy and re-create it. 
#
# Sep 11 2000 bug correction
#  - == tests replaced by eq test (string always equate to 0 was a bug)
#    (startup message was wrong).
#  - There were missing empty string tests.
#  - I fixed the undef($FILES{$line}) with some of those tests above. 
#
# Sept 12 2000
#  - Extended messages for soft-links, sockets, FIFO pipe.
#  - Added recursive tree search
#  - Checked all code using perl -w
#  - Added protection masks and uid/gid check glued to the checksum
#
# March 15 2001
#  - added flock() and proper protection mask for increased security
#  - added my Email and -i option.
#  - added -q option (really usefull for initialization)
#  - added check for OS/perl version which returns true on -d if
#    the file is a link to a directory. Use to cause an infinit
#    recursive tree search.
#
#
$AUTHOR  = "Jerome LAURET <jlauret\@mail.chem.sunysb.edu>";
$VERSION = "01-107";

# Name of the configuration file. 
# You should not have to change this ...
$CONF = "/usr/local/etc/watchfrog.conf";     

# Directory where the result file will be
$OPATH="/var/adm/";

# Where the chksum program is installed
# This can me replaced by md5sum which produces
# the same results.
$CHKSUM="/usr/local/bin/chksum -m";

# Temp directory. Leading slash mandatory.
$TMP = "/tmp/";

# Name of the mailer program (Unix only)
$MAILER="/usr/bin/Mail";

# Sleep time. This procedure is not suitable for
# cron-jobs. We need to have certain symbols constantly
# in memory so it is better to detach this. However, if
# the sleep time is null, we will use a cron-job like
# approach. 
# Default is set in default configuration file.
$SLEEP = 20;


# User to warn in case of problems. Email will be used ...
# Standard output is redirectd to a temporary file placed
# in /tmp . The result will be sent to $USER.
$USER = "%%RCPT%%";                  

# small test in case someone uses this script instead of doing
# the install first.
if($USER =~ m/RCPT/){ $USER = "root";}


# Nothing should be changed below this line
# -----------------------------------------------------------------------
$RECORD  = "watchfrog";
$CHKCLAST= "";     # Used to keep the chcksum of the .conf file
$CHKLLAST= "";     # Used to keep the chcksum of the .lis  file
$DEBUG   = 0;      # Manual debugging. Default is OFF.
$TILDA   = 0;      # Check files ending with tilda. Default is OFF
$SOCKET  = 1;      # Check for socket-files
$SHOWW   = 1;      # Show warnings we get from CHKSUM failure
$DODIR   = 1;      # Perform a md5sum on directories. Disabled on Linux.
$DOPIPE  = 0;      # Check the pipes file


# Scan for arguments
for ($i=0 ;$i <= $#ARGV ; $i++){
    $line = $ARGV[$i];
    if ( $line eq "-i"){
	# Asking for installation
	&Install();
	exit;
    } elsif ( $line eq "-u"){
	&UnInstall();
	exit;
    } elsif ( $line eq "-q"){
	# quit
	$SLEEP = -1;
    } elsif ( $line eq "-d"){
	# Debugging is ON
	$DEBUG = 1;
	# User will be stripped so no 
	# messages will be sent
	$USER  = "";
    } else {
	print "$0 :: Unknown argument $line\n";
    }
}


# The first time around, the conf file is created if it does
# not exists. This is done only once and is NOT in the 
# processing loop.
if( ! -e $CONF){
    ($path,$file) = ($CONF =~ m/(.*\/+)(.*)/);
    if($DEBUG){
	print "Path = $path\n";
	print "File = $file\n";
    }
    if( ! -d $path){
	if( mkdir($path,0755) ){
	    push(@WARNINGS,"$0 :: $path created");
	} else {
	    &mdie("$0 :: Cannot create $path. Abort !!\n");
	}
    }
    push(@WARNINGS,"$0 :: Creating initial configuration file");
    open(FO,">$CONF") || &mdie("$0 :: Cannot open $CONF as write\n");

    @DEFAULT = &Candidates();
    foreach $line (@DEFAULT){
	print FO "$line\n";
    }
    close(FO);
    # Immediate memory cleaning
    undef(@DEFAULT);
}



# Now load the .lis file containing all checksums.
# Above routine also checks that the file has not been modified,
# exists, first time etc ... It uses the global variable $CHKLLAST
&ReadListing("$OPATH$RECORD.lis");






# ----------------------------------------------------------------
# Processing loop.
# ----------------------------------------------------------------

NewPass:
if($USER eq ""){ print "Starting pass ...\n";}
if($DEBUG){ print "$CHKCLAST\n"};


# Basic check. CHKLAST is at 0 the first time around.
# This should indicate a new run.
if($CHKCLAST eq ""){
    push(@WARNINGS,
	 "\n$0 V$VERSION (checksum leap detection) starting on ".
	 localtime());
} 

# This script checks for its CONF file change ...
# before reading it.
# Double check existence of the configuration file.
if( ! -e $CONF){
    push(@WARNINGS,
	 "****************************************************\n".
	 "$0 :: ERROR : configuration file is gone !!\n".
	 "Missing $CONF\n".
	 "We cannot continue. Recreate the configuration file\n".
	 "or restart this procedure to set to default values\n".
	 "****************************************************\n");
} else {
    # Indentation is cosmetic ...
    $CHKBASE = &GetChksum("$CONF");
    if($CHKBASE ne $CHKCLAST && $CHKCLAST ne ""){
	push(@WARNINGS,
	     "$CONF checksum $CHKCLAST has changed to $CHKBASE");
    }
    $CHKCLAST = $CHKBASE;
}



# Now read the CONF file if possible ...
if( open(FI,$CONF) ){
    while ( defined($line = <FI>) ){
	chomp($line);

	if($line ne ""){
	    @items= split("#",$line);
	    if($#items != -1){
		$line = $items[0];
		$line =~ s/^\s*(.*?)\s*$/$1/;
	    } else {
		$line = "";
	    }
	}

	if( $line ne ""){
	    # Support inline comment
	    # Directives may be present. Re-split.
	    @items=split("=",$line);
	    $items[0] = uc($items[0]);

	    if( defined($items[1]) ){
		# a directive has been found
		if($items[0] eq "EXCLUDE"){
		    # Exclusion applies to files
		    # and directories. It will stopr ecursion.
		    $EXCLUDED{$items[1]} = 1;
		} elsif ($items[0] eq "SLEEP"){
		    if( $items[1] != 0){
			if($SLEEP > 0){
			    $SLEEP = $items[1];
			}
		    } else {
			push(@WARNINGS,
			     "$0 :: Sleep time=0 in configuration".
			     " not allowed\n");
		    }
		} elsif ($items[0] eq "TILDA"){
		    $TILDA = int($items[1]);
		} elsif ($items[0] eq "DOPIPE"){
		    $DOPIPE = int($items[1]);
		} elsif ($items[0] eq "SOCKET"){
		    $SOCKET= int($items[1]);
		} elsif ($items[0] eq "DODIR"){
		    $DODIR = int($items[1]);
		} elsif ($items[0] eq "SHOWW"){
		    $SHOWW = int($items[1]);
		} else {
		    push(@WARNINGS,"$0 :: Unknown directive $items[0]\n");
		}
	    } else {
		# An item is there
		push(@SCAN,$line);
	    }
	} # Blank or comment lines
    }
    close(FI);
} else {
    push(@WARNINGS,"$0 :: Could not open $CONF for read");
}





# Now, either we have a list of checksums or we don't.
# In all cases, we have a list of file and/or directory to scan.
foreach $line (@SCAN){
    # For each line in the SCAN recorded list, we need to expand wildcard
    # if any, read entire directories if there are directories etc...
    if ( -d $line){
	if ( substr($line,length($line)-1,1) eq "/"){
	    if($DEBUG){ print "$line is a directory (recursive)\n"};
	    # We will read all files recursively by pushing directories
	    # to the list ...
	    &ReadTree($line,0);
	} else {
	    if($DEBUG){ print "$line is a directory (static)\n"};
	    # Glob it. We will treat file exclusion in that directory 
	    # later
	    $line .= "/*";
	    push(@ALL,glob($line));
	}
    } elsif (-e $line){
	# Just a 
	if($DEBUG){ print "$line is a file, a socket a soft link or a pipe\n"};
	if( ! defined($EXCLUDED{$line}) ){
	    push(@ALL,"$line");
	}
    } else {
	# Expansion are like /Ram/*/ . Expansion does 
	# not lead to recursion.
	# But an expansion can lead to more directories
	if($DEBUG){ print "$line is an expansion\n"};
	push(@ALL,glob($line));
    }
}
undef(@SCAN);      # Delete the list of dir/files to check


# Now, we have all files in @ALL array.
# note : we will NOT treat soft link since we also want to
# detect any change in file soft-link. Of course, this
# implies that we will have to check some files multiple
# times ... 
foreach $line (@ALL){
    # Excluded directive check 
    #  old version will never be checked
    if( ! defined($EXCLUDED{$line}) ){
	# Tilda files check. See configuration.
	if( ($TILDA  || $line !~ /.*~/) &&
	    ($SOCKET || ! -S $line)     &&
	    ($DOPIPE || ! -p $line)     ){
	    
	    #if($DEBUG){ print "Checking $line\n";}
	    $chksum = &GetChksum("$line");
	    $CHECKED{$line} = 1;

	    $last = $FILES{$line};
	    if( ! $last  ){
		# This file is new. 
		# There are no previous checksums for it.
		@all1 = split(";",$chksum);
		$message = "uid $all1[2] gid $all1[3] mask ".&ShowPerms($all1[1]);
		if( -d $line){
		    push(@WARNINGS," NEW DIR  :: $line $message");
		} elsif ( -l $line) {
		    push(@WARNINGS," NEW LINK :: $line\@ -> ".readlink($line));
		} elsif ( -S $line){
		    push(@WARNINGS," NEW SOCK :: $line $message");
		} elsif ( -p $line){
		    push(@WARNINGS," NEW PIPE :: $line $message");
		} else {
		    push(@WARNINGS," NEW FILE :: $line $message");
		}
		undef(@all1);
		undef($message);

	    } else {
		if( $last ne $chksum ){
		    if($DEBUG){ print "[$last][$chksum]\n";}
		    # directories will not produce a MODIFIED
		    # message since they may be frequently updated. 
		    # This depends on the file system. 
		    # However, new files will be advertized in the above 
		    # loop ...
		    if( ! -d $line ){
			# What has changed ?
			@all1 = split(";",$last);
			@all2 = split(";",$chksum);
			$message =         " MODIFIED :: $line";
			if ($all1[0] ne $all2[0]){
			    $message .= " ; checksum changed";
			}
			if ($all1[1] ne $all2[1]){
			    $message .= " ; mask ".&ShowPerms($all1[1])." => ".&ShowPerms($all2[1]);
			} 
			if ($all1[2] ne $all2[2]){
			    $message .= " ; uid $all1[2] => $all2[2]";
			}
			if ($all1[3] ne $all2[3]){
			    $message .= " ; gid change $all1[3] => $all2[3]";
			}
			push(@WARNINGS,$message);
			undef($message);
			undef(@all1);
			undef(@all2);
		    }
		}
	    }
	    # Update the checksum
	    $FILES{$line} = $chksum;

	} # Tilda check 
    }     # Excluded directive 
}         # @ALL scan
undef(@ALL);


# Some files may not have been checked from the last pass.
foreach $line (keys %FILES){
    if( ! defined($CHECKED{$line}) && $FILES{$line} ne ""){
	# A file was in memory but was not checked
	push(@WARNINGS," WARNING  :: [$line] no longer present or checked");
	# undef() will lead to a hole in the list of files (keys)
	# This might be a bug in perl. We will correct for this
	# in the output. Corrected above by doing an extra $FILES{$line} ne ""
	# test.
	#if($DEBUG){ print "Undef   $line\n";}
	undef($FILES{$line});
    } else {
	#if($DEBUG){ print "defined $line\n";}
    }
}
undef(%CHECKED);   # Delete the list of files we have checked in this pass.



# Done. Dump results. Do some cleaning
&CreateListing("$OPATH$RECORD.lis");
&dump_warnings("$TMP$RECORD.log");
undef(@WARNINGS);  # Delete all warnings



if($USER eq ""){ print "Sleeping $SLEEP\n";}
if($SLEEP > 0){
    sleep($SLEEP);
    goto NewPass;
}


# ----
# Subs
# ----

# This subroutine will return a default configuration file.
# Each directory and/or files added will be checked prior to
# generating the default configuration. This will allow us to
# do a simple OS support (all in one + checks if present).
sub Candidates
{
    # Every line will be dumped as-is.
    my(@ALL)=(
	  "# $0 V$VERSION configuration file.",
          "# WatchFrog $VERSION configuration file.",
	  "# Written and (c) $AUTHOR",
	  "# and distributed under the terms of the GNU GPL license",
	  "#",
	  "# Path with or without wildcard or files can be added to",
	  "# this file ... Directory path without file specification",
	  "# will not check the directory but its full content and",
	  "# start a recursive check.",
	  "#",
	  "# Directives may be used to control the program's",
	  "# behavior. Directives are case insensitive.",
	  "",
	  "# Sleep time in between checks. WatchFrog is an infinit",
	  "# loop which waits this amount of seconds before checking",
	  "# again.",
	  "SLEEP=14400",
	  "",
	  "# Version i.e. files ending with ~ can be checked or not",
	  "# Valid variable values are 1 or 0 from now on.",
	  "# Default for TILDA is 0.",
	  "#TILDA=1",
	  "",
	  "# Socket is a switch which enables socket checking or not",
	  "# By default, we check for sockets. Default is 1. Socket are",
	  "# checked for checksum leap if enabled.",
	  "#SOCKET=0",
	  "",
	  "# This variable controls checking of pipes. However, pipes",
	  "# are checked only for creation time and the checksum test",
	  "# will not apply. Turning it ON is un-tested. Default is 0.",
	  "#DOPIPE=1",
	  "",
	  "# By default, we display warning on checksum failure.",
	  "# One can disable this using the variable below.",
	  "#SHOWW=0",
	  "",
	  "# By default, this utility attempts to apply a checksum on",
	  "# directories. It might fail to do so and is automatically",
	  "# disabled on Linux nodes. If it fails, set it to 0.",
	  "#DODIR=1",
	  "");

    # lines will be checked.
    my(@check)=(
	  "",
	  "# List of files to skip. ",
	  "# WARNING : DO NOT add files which does not exists !! ",
	  "# Otherwise, a hacker may replace them and you won't be able to ",
	  "# see the change happening ... You should add only files which",
	  "# are frequently updated such as the one below. Hacker cannot",
	  "# replace them since they are re-created by dameons.",
	  "# For directories, you should add only directories which are",
	  "# part of the OS and subject to sanity check.",
	  "EXCLUDE=/sbin/swapdefault",
	  "EXCLUDE=/etc/ntp.drift",
	  "EXCLUDE=/etc/ssh_random_seed",
	  "EXCLUDE=/etc/fdmns/",
	  "EXCLUDE=/etc/yp/",
	  "EXCLUDE=/etc/mail/",
	  "",
	  "/bin",
	  "/usr/bin/",
	  "/etc/",
	  "/sbin/",
	  "/usr/local",
	  "/usr/local/bin/",
	  "/usr/local/etc/",
	  "/usr/local/root/bin/",
	  "/usr/sbin/",
	  "/usr/ucb/");

    my($tmp,$line,@items);

    foreach $line (@check){
	if( substr($line,0,1) ne "#" && $line ne ""){
	    @items = split("=",$line);
	    if($#items > 0){
		# this is a directive. It has to be a directory
		# as is.
		$tmp = $items[1];
	    } else {
		# For directories, remove them if they are soft-linked
		if( -l $line){
		    # this will fail at later -e test.
		    $tmp = readlink($line);
		    $tmp = "$line (soft linked to $tmp)";
		} else {
		    $tmp = $line;
		}
	    }

	    # For both, ignore if does not exists.
	    if( -e $tmp){
		push(@ALL,$line);
	    } else {
		# We don't add it at all
		push(@ALL,"# $line");
	    }
	} else {
	    push(@ALL,$line);
	}
    }
    return @ALL;
}


# Read and Create listing file containing the checksums.
# This file will be kept in memory for safety. The only time
# it really serves us is when we start the first time ...
sub ReadListing
{
    my($list) = @_;
    my($line,$chksum);

    # Open and load the last version of it
    if ($CHKLLAST eq ""){
	if($USER eq ""){ print "$list (first) -> $CHKLLAST\n";}
	if ( ! -e "$OPATH$RECORD.lis"){
	    push(@WARNINGS,
		 "$0 appears to be running for the first time. ".
		 "No $list found.");
	} else {
	    # Then we have to open it and load it in memory
	    if (open(FI,"$list")){
		# the associative array %FILES will be
		# created here.
		while (  defined($line = <FI>) ){
		    # Support for blank lines
		    chomp($line);
		    if($line ne ""){
			$line =~ s/^\s*(.*?)\s*$/$1/;
			($chksum,$file) = split(" ",$line);
			if( $chksum ne "" && $file ne ""){
			    $FILES{$file} = $chksum;
			}
		    }
		}
		close(FI);
	    } else {
		push(@WARNINGS,
		     "$0 :: Could not open $list for read\n");
	    }
	    $CHKLLAST = &GetChksum("$list");
	}
    } else {
	if( ! -e "$list"){
	    # OK. We had a preceding pass but the file is no longer
	    # there. Someone has deleted it completly ...
	    push(@WARNINGS,
		 "****************************************************\n".
		 "$0 :: PLEASE, CHECK THIS IMMEDIATLY !!!\n".
		 "The file $list has disappeared !!!\n".
		 "If intentional , then ignore. We will re-create it.\n".
		 "****************************************************\n");
	} else {
	    # We will double check the checksum only. If it has
	    # changed, this means that someone is trying to trick 
	    # us ...
	    $chksum   = &GetChksum("$list");
	    if($USER eq ""){ print "$list (next) -> $chksum $CHKLLAST\n";}
	    if( $chksum ne $CHKLLAST){
		# Bad Boy !! Someone has tried to change it by hand
		push(@WARNINGS,
		     "****************************************************\n".
		     "$0 :: $list checksum \n".
		     "has changed. Someone has changed it by hand !!! ".
		     "We will ignore it and we will regenerate it after\n".
		     "this pass ... It is of the outmost importance that\n".
		     "you  review any other file checksum change ...\n".
		     "****************************************************\n");
	    }
	}
    }
}

sub CreateListing
{
    my($list) = @_;

    # Dump results
    if($DEBUG){ print "Done\n"};
    open(FO,">$list.tmp") || 
	&mdie("$0 :: Could not open $list.tmp for write\n");
    flock(FO,2);
    foreach $line (keys %FILES){
	if( defined($FILES{$line}) ) {
	    print FO "$FILES{$line} $line\n";
	}
    }
    close(FO);
    flock(FO,8);
    chmod(0600,"$list.tmp");

    if(-e "$list"){
	if( ! unlink("$list") ){
	    &mdie("$0 :: Could not delete old log $list\n");
	}
    }
    if (! rename("$list.tmp","$list")){
	&mdie("$0 :: Could not rename $list.tmp\n");
    } else {
	# let's be sure folks
	chmod(0600,$list);
	$CHKLLAST = &GetChksum("$list");
    }
}

# Get checksum and uid/gid + protections
sub GetChksum
{
    my($file) = @_;
    my($tmp,$result,@all);

    if( -d $file && ($^O eq "linux" || ! $DODIR) ) {
	# That's one OS which cannot perform md5 sum on directories
	# Use inode info to make a supposadly acceptable number.
	$result = (stat($file))[1];
	$result = sprintf("%32.32d",$result);
    } elsif( -p $file && ! $DOPIPE ) {
	$result = (stat($file))[1];
	$result = sprintf("%32.32d",$result);
    } else {
	chomp($result = `$CHKSUM '$file'`);
    }
    if( $? != 0){
	# Error as a return status ...
	$result = "00000000000000000000000000000000;0;0;0";
	if( ! defined($FILES{$line}) ){
	    if ($SHOWW){
		push(@WARNINGS," ERROR occured while checking $file");
	    }
	}
    } else {
	($result,$tmp) = split(" ",$result);
	@all = stat($file);
	if( $#all != -1){
	    # switch to octal
	    $result .= ";$all[2];$all[4];$all[5]";
	} else {
	    $result .= ";0;0;0";
	}
    }
    $result;
}


sub mdie
{
    my(@all) = @_;

    &dump_warnings();
    die @all;
}

# WARNINGS is a global array.
sub dump_warnings
{
    my($file) = @_;
    my($line);
    my($FO);

    if($#WARNINGS != -1){
	if( ! open(FO,">$file") ){
	    $FO = STDOUT;
	    print $FO "Could not open $file\n";
	} else {
	    $FO = FO;
	}
	foreach $line (@WARNINGS){
	    print $FO "$line\n";
	}
	if($DEBUG){ print "Closing file\n";}
	close($FO);

	if(! -z $file && -e $file){
	    if($USER ne ""){
		if($DEBUG){ print "Sending results\n";}
		if($^O eq "VMS"){
		    system("mail/nocc/subj=\"$0 result\" $file $USER");
		} else {
		    if($DEBUG){ print "Sending to $USER $file\n";}
		    system("$MAILER -s \"$0 result\" $USER < $file");
		}
	    } else {
		if($DEBUG){ print "See results above\n";}
		if($^O eq "VMS"){		
		    system("type/nopage $file");
		} else {
		    system("cat $file");
		}
	    }
	}
	#sleep(1);
	if(-e "$file"){
	    unlink("$file");
	}
    }
}

# We won't use glob here
sub ReadTree
{
    local($dir,$level)=@_;
    local($DIR,$file);
    
    $DIR = "DIR$level";
    if(substr($dir,length($dir)-1,1) ne "/"){
	$dir .= "/";
    }
    opendir($DIR,$dir);
    while( defined($file = readdir($DIR) )){
	if( ! -d "$dir$file"){
	    push(@ALL,"$dir$file");
	} else {
	    # Guess what. Some perl OS version gives -d test TRUE if -l and
	    # the link is a directory. Real BAD !!!
	    if( -l "$dir$file"){ $file = readlink("$dir$file");}
	    if( $file ne "." && $file ne ".." && $file !~ m/\.\./){
		# Directories can be excluded
		if( ! defined($EXCLUDED{"$dir$file/"}) ){
		    &ReadTree("$dir$file",$level+1);
		}
	    }
	}
    }
    closedir($DIR);
}

# This sub has been taken from fcheck software
# as-is (was too lazzy to get it done by myself)
sub ShowPerms
{
    local($mode) = @_;

    local(@perms) = ("---", "--x",  "-w-",  "-wx",  "r--",  "r-x",  "rw-",  "rwx");
    local(@ftype) = ("?", "p", "c", "?", "d", "?", "b", "?", "-", "?", "l", "?", "s", "?", "?", "?");
    local ($setids) = ($mode & 07000)>>9;
    local (@permstrs) = @perms[($mode & 0700) >> 6, ($mode & 0070) >> 3, ($mode & 0007) >> 0];
    local ($ftype) = $ftype[($mode & 0170000)>>12];
    if ($setids){
	# Sticky Bit?
	if ($setids & 01) { $permstrs[2] =~ s/([-x])$/$1 eq 'x' ? 't' : 'T'/e; }
	# Setuid Bit?
	if ($setids & 04) { $permstrs[0] =~ s/([-x])$/$1 eq 'x' ? 's' : 'S'/e; }
	# Setgid Bit?
	if ($setids & 02) { $permstrs[1] =~ s/([-x])$/$1 eq 'x' ? 's' : 'S'/e; }
    }
    return (join('', $ftype, @permstrs));
}


# -------------------------- INSTALLATION_RELATED_ONLY ------------------------------------
#
# This procedure will attempt to install WatchFrog
# on your computer. DO NOT remove the upper separator since
# this script will strip everything from there down for memory
# and space saving. 
#
sub Install()
{
    my($file);
    my($crun);
    local($location,$vrun,$initd);
    local(@CONFIG);


    print 
	"This installation currently assumes a Unix based OS\n\n",
	"Is your OS Unix based [y]/n ? ";

    if( &stdin("yes") !~ m/y/i){ 
	die "Other OS not yet implemented.\n";
    } else {
	if( getlogin() ne "root"){
	    die "Sorry ! This software MUST be installed under root account\n";
	}

	print 
	    "This procedure will try to autodetect the location of the required\n",
	    "files and directories and will also create /usr/local/etc if not\n",
	    "yet done. In case it cannot detect the proper directories, it will\n",
	    "ask you for it ... However, be aware that any illegal answer (non\n",
	    "existing directory or file) will make this installation procedure\n",
	    "to repeat the question until a valid and proper answer is offered.\n\n";
	print "Do you wish to proceed [y]/n ? ";
	if( &stdin("yes") !~ m/y/i){ die "Ok. Stopping now. Try again later maybe ?\n";}


	# This is required
	&RequireDir("/usr/local","0755");
	&RequireDir("/usr/local/etc","0755");


	# Mandatory 1
	# Locate the md5 capable checksum program
	print 
	    "\n",
	    "$RECORD MUST use a md5 aware checksum program. This is the only trusted\n",
	    "method for keeping a list of checksums.\n";
	if( ! -e "/usr/local/bin/chksum"){
	    if(-e "/usr/bin/md5sum"){
		$tmp = "/usr/bin/md5sum";
	    } elsif (-e "/usr/local/bin/md5sum"){
		$tmp = "/usr/local/bin/md5sum";
	    } else {
		do {
		    print "$RECORD requires a md5 checksum aware program.\n";
		    print "Which program to use for the md5 checksum ? ";
		    $tmp =  &stdin("");
		} while ( ! -x $tmp);
	    }
	    push(@CONFIG,"/usr/local/bin/chksum -m;$tmp");
	} else {
	    $tmp = "/usr/local/bin/chksum -m";
	}
	#
	# test this md5 capable checksum program
	#
	$file = "/tmp/$RECORD-install.tmp";
	if( -e $file){ unlink($file);}
	open(FO,">$file") || die "Could not open a temporary file $file\n";
	print FO "Hello world\n"; 
	close(FO); 
	chomp($res = `$tmp $file`);
	$res = (split(" ",$res))[0];
	if($res ne "f0ef7081e1539ac00ef5b761b4fb01b3"){
	    print "The expected answer was : f0ef7081e1539ac00ef5b761b4fb01b3\n";
	    print "The current answer  is  : $res\n\n";
	    print "Shall I continue y/[n] ? ";
	    if( &stdin("no") !~ m/y/i){ 
		die 
		    "Please, install chksum program and start this installation again. Program\n",
		    "available at http://nucwww.chem.sunysb.edu/htbin/software_list.cgi?package=chksum\n";
	    } else {
		print 
		    "The md5 checksum program you have chosen may be wrong ...\n",
		    "You are proceeding with the installation at your own risk ...\n";
	    }
	} else {
	    print " * md5 checksum using $tmp verified.\n";
	}
	print " + We will be using $tmp for md5 checksum verification\n";
	

	# Mandatory 2
	# Now, take care of the startup script
	print 
	    "\n",
	    "The way $RECORD works is as follow : the level3 startups immediatly\n",
	    "starts a checker which purpose is to start the WatchFrog real process\n",
	    "if it is not running. Of course, at startup it will not so be running\n",
	    "so it will indeed start it. Later, the crontab will also have an entry\n",
	    "for running the checker script periodically. In this phase, the purpose\n",
	    "of the checker is to auto-detect any interruption of service of the\n",
	    "as well as an eventual PID leap (i.e. someone stoping/restarting the\n",
	    "the daemon). As far as the daemon runs, the checksum list is SAFE i.e.\n",
	    "the listing file is just present to recover in between shutdown/power\n",
	    "ups. In fact, the daemon WILL NOT trust this file and might re-create\n",
	    "it at will if it has been modified.\n\n";
	print "Press return to continue ...";
	$tmp = &stdin("");

	if( -d "/sbin/init.d"){
	    $tmp = "/sbin/init.d";
	} elsif (-d "/etc/rc.d/init.d"){
	    $tmp = "/etc/rc.d/init.d";
	} else {
	    print "Where is your init.d directory ? ";
	    $tmp = &stdin("");
	}
	if($tmp eq "" || ! -d $tmp){ 
	    die "$RECORD cannot be installed if there is no init.d directory\n";
	}
	$initd = $tmp;
	print " + $initd will be used for the startup file\n";
	

	# Mandatory 3
	print 
	    "$RECORD sends email in case something is modified\n";
	if( ! -e $MAILER){ 
	    print "Cannot locate your mailer program, assumed to be $MAILER.\n";
	    do {
		print "Fully specified mailer program : ";
		$tmp = &stdin($MAILER);
	    } while ( ! -x $tmp);
	    push(@CONFIG,"/usr/bin/Mail;$tmp");
	} else {
	    $tmp = $MAILER;
	}
	print " + mailer program is $tmp\n";
	print "Account to send reports too [root] ? ";
	$tmp = &stdin("root");
	push(@CONFIG,"%%RCPT%%;$tmp");
	print " + account which will receive the Emails is  $tmp\n";



	# Misc file location
	# See where we can put the log file
	print 
	    "\n",
	    "$RECORD uses a log file where all the checksum are kept\n";
	if(-d "/var/adm"){
	    print " - We will use /var/adm for the log file location\n";
	} elsif (-d "/var/log"){
	    print " - We will use /var/log for the log file location\n";
	    push(@CONFIG,"/var/adm;/var/log");
	} else {
	    do {
		print "Directory where the log file should be [/etc] ? ";
		$tmp = &stdin("/etc");
	    } while (! -d $tmp);
	    print " - We will use $tmp for the log file location\n";  
	    push(@CONFIG,"/var/adm;$tmp");
	}



	# See where the pid file will go
	print 
	    "\n",
	    "$RECORD also uses a PID file to keep track of stopped/restarted daemon\n";
	if(-d "/var/run"){
	    $vrun = "/var/run";
	} else {
	    do {
		print "$RECORD will start a daemon. Where does the pid files reside ? ";
		$vrun = &stdin("/etc");
	    } while (! -d $vrun);
	}
	print " - The pid file will be in $vrun\n";



	# Where to install myself
	print
	    "\n",
	    "$RECORD also install itself and the required cron script in\n",
	    "the directory of your choice.\n\n";
	do {
	    print "Where should I install the main script (no default) ? ";
	    $location = &stdin("");
	} while ($location eq "" || ! -d $location);
	print " - Installation will occur in $location\n";




	# Now, create required files
	if( ! -e "$initd/WFrog"){
	    &WriteFrogFile("$initd/WFrog",0);
	    print " * $initd/WFrog created.\n";
	} else {
	    print " - $initd/WFrog already exists. Replace y/[n] ? ";
	    if( &stdin("n") =~ m/y/i){ &WriteFrogFile("$initd/WFrog",0);}
	}
	if( ! -e "$location/frogcron"){
	    &WriteFrogFile("$location/frogcron",0);
	    print " * $location/frogcron created.\n";
	} else {
	    print " - $location/frogcron already exists. Replace y/[n] ? ";
	    if( &stdin("n") =~ m/y/i){ &WriteFrogFile("$location/frogcron",0);}
	}
	# and install itself. More tricky ...
	if( ! -e "$location/WatchFrog"){
	    &WriteFrogFile("$location/WatchFrog",1);
	    print " * $location/WatchFrog created.\n";
	} else {
	    print " - $location/WatchFrog already exists. Replace y/[n] ? ";
	    if( &stdin("n") =~ m/y/i){ &WriteFrogFile("$location/WatchFrog",1);}
	}


	# take care of startup soft links in the rc.d directories.
	# i.e. the K21WFrog and S56WFrog.
	$tmp = $initd;
	$tmp =~ s/(.*\/+)(.*)/$1/;
	if( ! -e "$tmp"."rc0.d/K21WFrog"){
	    symlink("$initd/WFrog","$tmp"."rc0.d/K21WFrog");
	    print " * $tmp"."rc0.d/K21WFrog created\n";
	} else {
	    print " - $tmp"."rc0.d/K21WFrog already exists.\n";
	}
	if( ! -e "$tmp"."rc3.d/S56WFrog"){
	    symlink("$initd/WFrog","$tmp"."rc3.d/S56WFrog");
	    print " * $tmp"."rc3.d/S56WFrog created\n";
	} else {
	    print " - $tmp"."rc3.d/S56WFrog already exists.\n";
	}
	
	# Now, we only have the crontab to take car of ...
	print 
	    "\n",
	    "Installation is almost complete. Shall I modify your crontab now and add\n",
	    "a line for the periodic excution of $location/frogcron y/[n] ? ";
	if( &stdin("no") =~ m/y/i){
	    chomp($tmp = `crontab -l | grep frogcron`);
	    if($tmp eq ""){
		$file = "/tmp/$RECORD-crontab.tmp";
		if( -e $file){ unlink($file);}
		system("crontab -l >$file");
		open(FO,">>$file") || die "Could not open $file in append mode\n";
		print FO 
		    "# The following line is required for the WatchFrog utility\n",
		    "# version $VERSION, (c) $AUTHOR\n",
		    "02,17,32,47 * * * * $location/frogcron\n";
		close(FO);
		system("crontab $file");
		unlink($file);
		print " * Your crontab has been modified\n";
	    } else {
		print " - Your crontab is already up-to-date\n";
	    }
	} else {
	    print 
		"\n",
		"You should add the following line to your crontabs\n",
		"02,17,32,47 * * * * $location/frogcron\n",
		"\n";
	}
	print 
	    "The first time you install this utility, a configuration file will appear as\n",
	    "/usr/local/etc/$RECORD.conf. Read the comments therein and change it for your\n",
	    "site ... If you have NOT setup the crontabs yet, do it NOW manually to complete\n",
	    "the installation of this software.\n\n",
	    "For testing purposes and/or initialization, use\n",
	    " % $location/WatchFrog -d -q\n\n";
	
	print "Installation is DONE\n";

    }
}

sub WriteFrogFile
{
    my($file,$flag)=@_;
    my($date)="".localtime();
    my($user)=getlogin();
    my($tmpl);
    my($ok)=1==1;
    my(@items,$line);

    $tmpl = $file;
    $tmpl =~ s/.*\///g;
    open(FI,$tmpl)        || die "Template file $tmpl does not exsists\n";
    open(FO,">$file")     || die "Could not open $file\n";
    while( defined($line = <FI>)){
	chomp($line);
	if($flag == 1){
	    if($line =~ m/INSTALLATION_RELATED_ONLY/){
		# strip this out
		$ok = 1==0;
	    } else {
		$line =~ s/%%DATE%%/$date/;
		$line =~ s/%%USER%%/$user/;
		foreach $el (@CONFIG){
		    @items = split(";",$el);
		    $line =~ s/$items[0]/$items[1]/;
		}
	    }
	} else {
	    $line =~ s/%%AUTHOR%%/$AUTHOR/;
	    $line =~ s/%%DATE%%/$date/;
	    $line =~ s/%%USER%%/$user/;
	    $line =~ s/%%LOC%%/$location/;
	    $line =~ s/%%RUN%%/$vrun/;
	}
	if($ok){ print FO "$line\n";}
    }
    if($flag == 1){
	# Replace the Install()/UnInstall() routines
	print FO "sub Install\n{\n  print \"Use the initial distribution\\n\";\n}\n";

	print FO "sub UnInstall{\n\tmy(\$dir)=\"$location\";\n\tmy("; 
	print FO "\$found)=1==0;\n\tsystem(\"crontab -l >/tmp/$RECORD-cron.tmp"; 
	print FO "\");\n\topen(FI,\"/tmp/$RECORD-cron.tmp\");\n\topen(FO,\">";
	print FO "/tmp/$RECORD-cron.tmp2\");\n\twhile( defined(\$line = <FI>";
	print FO ") ){\n\t    chomp(\$line);\n\t    if(\$line !~ m/\$dir";
	print FO "/){ print FO \"\$line\\n\";}\n\t    else { \$found = 1==1";
	print FO ";}\n\t}\n\tif(\$found){ system(\"crontab /tmp/$RECORD-cr";
	print FO "on.tmp2\");}\n\tsystem(\"$initd/WFrog stop\");\n\tu";
	print FO "nlink(\"$vrun/watchfrog.pid\") if (-e \"$vrun/watchfrog.pid\")";
	print FO ";\n\tunlink(\"$location/frogcron\")  if (-e \"$location/fro";
	print FO "gcron\");\n\tunlink(\"$location/WatchFrog\") if (-e \"$location";
	print FO "/WatchFrog\");\n\tunlink(\"$initd/../rc3.d/S56WFrog\") ";
	print FO "if (-e \"$initd/../rc3.d/S56WFrog\");\n\tunlink(\"$initd/..";
	print FO "/rc0.d/K21WFrog\") if (-e \"$initd/../rc0.d/K21WFrog\");\n";
	print FO "\tunlink(\"$initd/WFrog\") if (-e \"$initd/WFrog\"); \n";
	print FO "}\n";

    }
    close(FO);
    close(FI);
    chmod(0700,$file);
}


sub RequireDir
{
    my($dir,$mask)=@_;
    if( ! -d $dir){
	mkdir($dir,$mask);
	print " - $dir created\n";
    }
}

sub stdin
{
    my($dans)=@_;

    my($rep);
    $rep = <STDIN>;
    $rep =~ s/^\s*(.*?)\s*$/$1/;
    $rep =~ s/\s+/ /g;
    if($rep eq "" && defined($dans) ){
	$dans;
    } else {
	$rep;
    }
}


sub UnInstall
{
    print "Nothing installed yet\n";
}

