#!/usr/bin/perl
#############################################################################
#                                                                           #
# Copyright (C) 1996 Michael A. Gumienny                                    #
#                                                                           #
# This program is free software; you can redistribute it and/or modify it   #
# under the terms of the GNU General Public License as published by the     #
# Free Software Foundation; either version 2 of the License, or (at your    #
# option) any later version.                                                #
#                                                                           #
# This program is distributed in the hope that it will be useful, but       #
# WITHOUT ANY WARRANTY; without even the implied warranty of                #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General #
# Public License for more details.                                          #
#                                                                           #
# You should have received a copy of the GNU General Public License along   #
# with this program; if not, write to:                                      #
#                                                                           #
#      Free Software Foundation, Inc.                                       #
#      59 Temple Place - Suite 330                                          #
#      Boston, MA 02111-1307, USA.                                          #
#                                                                           #
# Or you can find the full GNU GPL online at: http://www.gnu.org            #
#                                                                           #
# Please send your comments, updates, improvements, wishes and bug reports  #
# for fcheck to:                                                            #
#                                                                           #
#      Michael A. Gumienny           gumienny@hotmail.com                   #
#                                                                           #
#############################################################################

#############################################################################
#                                                                           #
# File:  fcheck                                                             #
#                                                                           #
# Usage: fcheck [-acdilv] [host] [directory]                                #
#                                                                           #
# $Id: fcheck,v 7.37 1999/08/04 22:51:00 root Exp root $                    #
#                                                                           #
# Description:                                                              #
#        Used to validate creation dates of critical system files, and as   #
#        a baseline system verification script.                             #
#                                                                           #
# Options:                                                                  #
#        -a    Automatic mode, do all directories in configuration file.    #
#        -c    Create a new base line database for the given directory.     #
#        -d    Directory names are to be monitored for changes also.        #
#        -i    Ignore creation times, check permissions, adds, deletes.     #
#        -l    Log information to logger rather than stdout messages.       #
#        -v    Verbose mode, not used for report generation.                #
#                                                                           #
# Author: Michael A. Gumienny                                               #
#                                                                           #
# Written: 1996                                                             #
#                                                                           #
#############################################################################

#############################################################################
# $Log: fcheck,v $
# Revision 2.07.37  1999/08/04 22:51:00  root
# Minor changes to permissions routines
#
# Revision 2.07.34  1999/07/29 01:11:13  root
# Now Works under windows
#
# Revision 2.06.27  1999/07/24 14:49:04  root
# Initial checkin for migration to DOS PERL
#

#############################################################################
#                                                                           #
#                 User modifiable variable definitions:                     #
#                                                                           #
#############################################################################
$config="/usr/local/etc/fcheck.cfg";
#$config="C:/Work/fcheck/fcheck.cfg";

#############################################################################
#                                                                           #
#      Non-User modifiable variable definitions: (DO NOT MODIFY THESE!)     #
#                                                                           #
#############################################################################
undef($Auto);
undef($Verbose);
undef($BaseLine);
undef($CreateDate);
undef($DirCheck);
undef($Logging);
undef($DOS);
($Me)=split("/", reverse($0));
($Me)=split(" ", reverse($Me));

#############################################################################
# &Help;                                                                    #
# This routine explains brief usage syntax to STDOUT. The program is then   #
#  terminated.                                                              #
#############################################################################
sub Help
        {
        open(ME, "<$Me");
        for ($i=0; $i<38; $i++) { $_=<ME>; }
        close(ME);

        $_ = substr($_, 15, 16);

        printf("Usage:\t%s [-acdilv] [directory]\n", $Me);
        printf("\tUsed to validate creation dates of critical system files.\n\n");
        printf("\tVersion: %s\n\n", $_);
        printf("\tOptions:\n");
        printf("\t-a\tAutomatic mode, do all directories in configuration file.\n");
        printf("\t-c\tCreate base-line database.\n");
        printf("\t-d\tDirectory names are to be monitored for changes also.\n");
        printf("\t-i\tIgnore create dates, check permissions, additions, deletions.\n");
        printf("\t-l\tLog information to logger rather than stdout messages.\n");
        printf("\t-v\tVerbose mode.\n\n");
        exit(0);
        }



#############################################################################
# &Configure;                                                               #
# This routine reads in the configuration file, and initializes user        #
# definable variables.                                                      #
#############################################################################
sub Configure
        {
        if ($Verbose) { printf("debug: Attempting to determine OS and hostname.\n"); }
        # Try to determine the OS that we're running on based on environment
        # variables that do not normally exist on UNIX platforms but do in DOS.
        # This can be set in the config file to by pass this routine.
        #
        if ($ENV{'COMSPEC'} && $ENV{'CMDLINE'})
                {
                # I think this system is DOS
                ++$DOS;
                # hostname not included with win3.x, win95/98, but is for NT, so...
                $ThisHost = $ENV{'HOSTNAME'} unless $ThisHost;
                }
        else
                {
                # I think this system is UNIX based
                if(!$ThisHost)
                        {
                        $ThisHost = `hostname`;
                        chop($ThisHost);
                        }
                }
        # If no hostname given, default to "localhost" as our name
        $ThisHost = "localhost" unless $ThisHost;
        if ($Verbose)              { printf("debug: Set hostname to: $ThisHost.\n"); }
        if (($Verbose) && ($DOS))  { printf("debug: OS is DOS based.\n"); }
        if (($Verbose) && (!$DOS)) { printf("debug: OS is UNIX based.\n"); }
        if ($Verbose) { printf("debug: (Configure) Reading configuration file.\n"); }
        open(CONFIG, "<$config") || &Error("Can't find $config");
        while(<CONFIG>)
                {
                next if /^#/ || /^\n/;
                chop;
                ($KeyWord, $Variable)=m/(\w+)\s*=\s*(.*)/;
                $KeyWord = &toupper($KeyWord);
                if ( $Verbose) { printf("debug: Key:%s\tValue:%s\n", $KeyWord, $Variable); }
                if ( $KeyWord eq "DIRECTORY") { push(@CheckDir, $Variable); }
                if ( $KeyWord eq "EXCLUSION") { if($DOS) { $Variable = &toupper($Variable); } push(@ExcludeFile, $Variable); }
                if ( $KeyWord eq "LOGGER")    { $Logger = $Variable; }
                if ( $KeyWord eq "DATABASE")  { $DBDir = $Variable; }
                if ( $KeyWord eq "HOSTNAME")  { $ThisHost = $Variable; }
                if (($KeyWord eq "SYSTEM") && (&toupper($Variable) eq "DOS"))  { $ENV{'CMDLINE'}="null"; }
                if (($KeyWord eq "SYSTEM") && (&toupper($Variable) eq "UNIX")) { $ENV{'CMDLINE'}=""; }
                if ( $KeyWord eq "TIMEZONE")  { $ENV{'TZ'}=&toupper($Variable); }
                }
        if (!$ENV{'TZ'}) { printf("Error: Need TZ environment configured or environment set!\n\n"); &Help; }
        }
 


#############################################################################
# &Exclude($filename);                                                      #
#############################################################################
sub Exclude
        {
        local($File)=@_;
        if($Verbose) { printf("debug: (Exclude) checking on %s\n", $File); }
        foreach $Line (@ExcludeFile)
                {
                if($DOS) { $File = &toupper($File); }
                if($Verbose) { printf("debug: (Exclude) against %s\n", $Line); }
                if ($Line =~ /\/$/)
                        { # Sub-directory matches
                        if ($File =~ /^$Line/) { if($Verbose) { printf("debug: (Exclude) directory match found\n"); } return(1); }
                        }
                else
                        { # A file matches
                        if ($File eq $Line) { if($Verbose) { printf("debug: (Exclude) file match found\n"); } return(1); } }
                        }
                return(0);
        }



#############################################################################
# &BuildBaseline($dir);                                                     #
#############################################################################
sub BuildBaseline
        {
        local($DBfile)=@_;
        if($DOS) { $DBfile =~ s/:/.drive/; } # Handle drive delimiters C:, D:, E:, etc.
        $DBfile =~ s/\//_/g;
        $DBfile = sprintf("%s.%s", $ThisHost, $DBfile);
        if ($Verbose) { printf("debug: (BuildBaseLine) building baseline %s\n", $DBfile); }
        open (DB, ">$DBDir/$DBfile") || &Error("no base file directory exist! [$DBDir]");
        foreach $Line (@LiveDataDir) { printf(DB "%s\n", $Line); }
        close(DB);
        return 0;
        }



#############################################################################
# &GetDB($dir);                                                             #
# This routine builds the array @BaseLineDir from the database files.       #
#############################################################################
sub GetDB
        {
        local($DBfile)=@_;
        local($BI,$BP);
        if($DOS) { $DBfile =~ s/:/.drive/; } # Handle drive delimiters, C:, D:, E:, etc.
        $DBfile =~ s/\//_/g;
        $DBfile = sprintf("%s.%s", $ThisHost, $DBfile);
        if ($Verbose) { printf("debug: (GetDB) reading %s/%s\n", $DBDir, $DBfile); }
        open (DB, "<$DBDir/$DBfile") || &Error("no base file(s) exist! [$DBfile]");
        while(<DB>)
                {
                chop;
                if($Verbose) { printf("debug: (GetDB) reading [%s]\n", $_); }
                ($BI, $BP) = split("!", $_);
                if(!$DirCheck) { next if ( $BP =~ /^d/); }
                push(@BaseLineDir, $_);
                }
        close(DB);
        return(@BaseLineDir);
        }



#############################################################################
# &GetLive($dir);                                                           #
# This routine builds the array @LiveDataDir based on the live data.        #
#############################################################################
sub GetLive
        {
        local($Dir)=@_;
        local($filename);
        if ($Verbose) { printf("debug: (GetLive) reading %s\n", $Dir); }
        if ($Dir =~ /:\/$/) { &GetDir($Dir, 0); }
        elsif (($Dir =~ /\/$/) && ($Dir ne "/")) { $Dir =~ s/\/$//; &GetDir($Dir, 1); }
        else { &GetDir($Dir, 0); }
        next if (&Exclude($Name));
        }



###############################################################################
# $x=&Scan_Build;                                                             #
# This is the heart of the script. Scan_Build will either scan and/or build a #
# databse of files based on the configuration file and what parms were passed #
###############################################################################
sub Scan_Build
{
  local($Hacks);
  $Hacks=0;
  if (!$BaseLine)
    {
    # Scan directory
    &GetDB($Dir);
    &GetLive($Dir);
    for ($ldd=0; $ldd<$#LiveDataDir + 1; $ldd++)
      {
      ($L_Inode, $L_Perms, $L_Size, $L_Time, $L_Name) = split("!", @LiveDataDir[$ldd]);
      for ($bld=0; $bld<$#BaseLineDir + 1; $bld++)
        {
        ($B_Inode, $B_Perms, $B_Size, $B_Time, $B_Name) = split("!", @BaseLineDir[$bld]);
        next if ($B_Name ne $L_Name);
        if ($B_Name eq $L_Name)
          {
          if ($CreateDate)
            {
            # Ignore creation dates
            if (($L_Perms ne $B_Perms) || ($L_Inode ne $B_Inode))
              {
              if($Logging)
		{
                $cmd=sprintf("%s -t %s WARNING: [%s] %s [%s  %s  %s  %s  %s]  Was modified to reflect the following:  [%s  %s  %s  %s  %s]\n",
                $Logger, $Me, $ThisHost, $L_Name, $B_Inode, &ShowPerms($B_Perms),
                $B_Size, &ctime($B_Time), $B_Name, $L_Inode, &ShowPerms($L_Perms),
                $L_Size, &ctime($L_Time), $L_Name);
		system($cmd);
		}
	      else
		{
                printf("\n\tWARNING: [%s] %s\n", $ThisHost, $L_Name);
                printf("\tInode\tPermissons\tSize\tCreated On\t\tName\n");
                printf("\t%s\t%s\t%s\t%s\t%s\n",
                  $B_Inode, &ShowPerms($B_Perms), $B_Size, &ctime($B_Time), $B_Name);
                printf("\t** Was modified to reflect the following: **\n");
                printf("\t%s\t%s\t%s\t%s\t%s\n",
                  $L_Inode, &ShowPerms($L_Perms), $L_Size, &ctime($L_Time), $L_Name);
		}
	      ++$Hacks;
	      }
            }
          else
	    {
	    # Don't Ignore creation dates
            if (($L_Time ne $B_Time) || ($L_Size ne $B_Size) ||
                ($L_Perms ne $B_Perms) || ($L_Inode ne $B_Inode))
	      {
	      if($Logging)
		{
                $cmd=sprintf("%s -t %s WARNING: [%s] %s [%s  %s  %s  %s  %s]  Was modified to reflect the following:  [%s  %s  %s  %s  %s]\n",
                $Logger, $Me, $ThisHost, $L_Name, $B_Inode, &ShowPerms($B_Perms),
                $B_Size, &ctime($B_Time), $B_Name, $L_Inode, &ShowPerms($L_Perms),
                $L_Size, &ctime($L_Time), $L_Name);
		system($cmd);
		}
	      else
		{
                printf("\n\tWARNING: [%s] %s\n", $ThisHost, $L_Name);
                printf("\tInode\tPermissons\tSize\tCreated On\t\tName\n");
                printf("\t%s\t%s\t%s\t%s\t%s\n",
                  $B_Inode, &ShowPerms($B_Perms), $B_Size, &ctime($B_Time), $B_Name);
                printf("\t** Was modified to reflect the following: **\n");
                printf("\t%s\t%s\t%s\t%s\t%s\n",
                  $L_Inode, &ShowPerms($L_Perms), $L_Size, &ctime($L_Time), $L_Name);
		}
	      ++$Hacks;
	      }
            }
	  @BaseLineDir[$bld] = "";
	  @LiveDataDir[$ldd] = "";
	  }
	}
      }

    # Check for files that have been added
    foreach $Live (@LiveDataDir)
      {
      next if ($Live eq "");
      ($Inode, $Perms, $Size, $Time, $Name) = split("!", $Live);
      if($Logging)
	{
        $cmd=sprintf("%s -t %s WARNING: File addition: [%s] %s  [%s  %s  %s  %s  %s]\n",
        $Logger, $Me, $ThisHost, $Name, $Inode, &ShowPerms($Perms), $Size, &ctime($Time), $Name);
	system($cmd);
	}
      else
	{
        printf("\n\tADDITION: [%s] %s\n", $ThisHost, $Name);
        printf("\tInode\tPermissons\tSize\tCreated On\t\tName\n");
        printf("\t%s\t%s\t%s\t%s\t%s\n",
          $Inode, &ShowPerms($Perms), $Size, &ctime($Time), $Name);
        }
      ++$Hacks;
      }

    # Check for files that have been deleted
    foreach $Base (@BaseLineDir)
      {
      next if ($Base eq "");
      ($Inode, $Perms, $Size, $Time, $Name) = split("!", $Base);
      if($Logging)
	{
        $cmd=sprintf("%s -t %s WARNING: File deletion: [%s] %s  [%s  %s  %s  %s  %s]\n",
        $Logger, $Me, $ThisHost, $Name, $Inode, &ShowPerms($Perms), $Size, &ctime($Time), $Name);
	system($cmd);
	}
      else
	{
        printf("\n\tDELETION: [%s] %s\n", $ThisHost, $Name);
        printf("\tInode\tPermissons\tSize\tCreated On\t\tName\n");
        printf("\t%s\t%s\t%s\t%s\t%s\n", $Inode, &ShowPerms($Perms), $Size, &ctime($Time), $Name);
        }
      ++$Hacks;
      }
    }
  else
    {
    # Build baseline database
    &GetLive($Dir);
    &BuildBaseline($Dir);
    undef(@BaseLineDir);
    undef(@LiveDataDir);
    return(1);
    }
  return($Hacks);
}



###############################################################################
# &GetDir($dir, $recurse);                                                    #
# This routine will build the @LiveData array from the information in $dir,   #
# optionally this routine will recurse down that directory tree.              #
###############################################################################
sub GetDir
	{
        local($rootdir, $r)=@_;
        opendir(DIR, $rootdir) || die "debug: (GetDir) No can do ($rootdir)...\n";
	foreach (sort readdir(DIR))
		{
		next if (/^\.\.?$/);
                $filename = $_;
                $filename = "$rootdir/$filename";

                # DOS chokes when the root directory gets a double slash prepended
                $filename =~ s/\/\//\//;
                #       0  dev      device number of filesystem
                #       1  ino      inode number
                #       2  mode     file mode  (type and permissions)
                #       3  nlink    number of (hard) links to the file
                #       4  uid      numeric user ID of file's owner
                #       5  gid      numeric group ID of file's owner
                #       6  rdev     the device identifier (special files only)
                #       7  size     total size of file, in bytes
                #       8  atime    last access time since the epoch
                #       9  mtime    last modify time since the epoch
                #      10 ctime    inode change time (NOT creation time!) since the epoch
                #      11 blksize  preferred block size for file system I/O
                #      12 blocks   actual number of blocks allocated
                #      (The epoch was at 00:00 January 1, 1970 GMT.) 
                ($x,$Inode,$Perms,$NLink,$Uid,$Gid,$x,$Size,$ATime,$MTime,$CTime,$BlkSize,$Blocks) = stat("$filename");
                next if (&Exclude($filename));

                # DOS names are really all uppercase, so...
                if($DOS)
                  { push(@LiveDataDir, join("!", " ", "          ", $Size, $CTime, &toupper($filename) )); }
                else
                  { push(@LiveDataDir, join("!", $Inode, $Perms, $Size, $CTime, $filename)); }

                if (($Verbose) && (!$DOS))
                  {
                  printf("debug: (GetDir) %s %s %s %s %s %s\n",
                  $Inode, $Perms, $Month, $Day, $Time, $filename);
                  }
                if (($Verbose) && ($DOS))
                  {
                  printf("debug: (GetDir) %s %s %s\n",
                  $Size, $Time, $filename);
                  }

                if ((-d "$filename" && !-l "$filename") && ($r)) { &GetDir("$filename", 1); }
		}
	close(DIR);
	}



###############################################################################
# $x=&toupper($string);                                                       #
# This small support routine will return the $string in uppercase format.     #
###############################################################################
sub toupper
        {
        local($x) = @_;
        $x =~ tr/a-z/A-Z/;
        return $x;
        }



###############################################################################
# $x=&ctime($y);                                                              #
# This support routine will return the converted time to human readable format#
# Basically, I'm trying to get away from any functions that may not be in any #
# very minimal PERL distribution.                                             #
###############################################################################
sub ctime
        {
        local($time) = @_;
        local($[) = 0;
        local($sec, $min, $hour, $mday, $mon, $year, $wday);

        @WeekDay = ('Sun','Mon','Tue','Wed','Thu','Fri','Sat');
        @Month   = ('Jan','Feb','Mar','Apr','May','Jun',
                    'Jul','Aug','Sep','Oct','Nov','Dec');

        ($sec, $min, $hour, $mday, $mon, $year, $wday) = ($TZ) ? gmtime($time) : localtime($time);
        if ($DOS)
                {
                # Present the times in the traditional DOS way...
                sprintf("%02d-%02d-%02d %02d:%02d%s", $mon, $mday, $year, $hour, $min, ($hour >=12) ? "p" : "a");
                }
        else
                {
                # Present the times in the traditional UNIX way...
                $year += ($year < 70) ? 2000 : 1900;
                sprintf("%s %02d %02d:%02d %4d", $Month[$mon], $mday, $hour, $min, $year);
                }
        }



###############################################################################
# $x=&ShowPerms($y);                                                          #
# This routine is a fairly simplistic approach (Hey, it works!) to converting #
# the returned "stat" call values to the more readable "rwx" format of UNIX.  #
###############################################################################
sub ShowPerms
	{
        local($mode) = @_;
        if($DOS) { return("\t"); }
	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));
	}



###############################################################################
# $x=&uid($y);                                                                #
# This small support routine will return a user ID name for a given uid #.    #
###############################################################################
sub uid
        {
	local ($uid) = @_;
        if ($DOS) { return "root"};
	local ($name) = getpwuid ($uid);
	defined ($name) ? return $name : return $uid;
        }



###############################################################################
# $x=&gid($y);                                                                #
# This small support routine will return a group ID name for a given gid #.   #
###############################################################################
sub gid
        {
        local ($gid) = @_;
        if ($DOS) { return "system"};
	local ($name) = getgrgid ($gid);
	defined ($name) ? return $name : return $gid;
        }



###############################################################################
# &Error("string");                                                           #
# This routine prints out critical errors and terminates execution.           #
###############################################################################
sub Error
        {
        printf("%s: %s\nterminating...\n\n", $Me, @_);
        exit(1);
        }



###############################################################################
# Main routine starts here.                                                   #
###############################################################################
# Parse the command line for arguments and flags
if($#ARGV==-1){&Help;} # help the user, they forgot the syntax, otherwise...

if(($#ARGV==0) && (@ARGV[0] !~ /^-/))
        { $Dir  = shift(@ARGV); }
else
        {
        foreach $arg (@ARGV)
                {
                if ($arg =~ /^-/)
                        {
                        if ($arg =~ /a/)  { $Auto=1; }
                        if ($arg =~ /c/)  { $BaseLine=1; }
                        if ($arg =~ /d/)  { $DirCheck=1; }
                        if ($arg =~ /i/)  { $CreateDate=1; }
                        if ($arg =~ /l/)  { $Logging=1; }
                        if ($arg =~ /v/)  { $Verbose=1; }
                        }
                }
        shift;
        $Dir  = shift(@ARGV);
        }

# Give user syntax help
if (($Dir eq "") && (!$Auto)) { &Help; }

$Dir =~ s/\\/\//g;

if ($BaseLine) { $DirCheck=1; }
&Configure;
if ($Verbose) { printf("debug: Processing host [%s]\n", $ThisHost); }

if ($Auto)
        {
        foreach $Dir (@CheckDir)
                {
                if (!$BaseLine)
                        {
                        if (!$Logging)
                                { printf("\nPROGRESS: validating integrity of %s\nSTATUS: ", $Dir); }
                        }
                if($Verbose) { printf("\nbuilding baseline for %s\n", $Dir); }
                if ((!&Scan_Build) && (!$Logging)) { printf("passed...\n\n"); }
                undef(@BaseLineDir);
                undef(@LiveDataDir);
                }
        }
else
        {
        if (!$BaseLine)
                {
                if (!$Logging)
                        { printf("\nPROGRESS: validating integrity of %s\nSTATUS: ", $Dir); }
                }
        if($Verbose) { printf("\nbuilding baseline for %s\n", $Dir); }
        &Scan_Build;
        }

