#!/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 [-acdfhilrsvx] [configuration file] [directory] # # # # $Id: fcheck,v 2.7.54 2000/11/07 12:33:32 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. # # -f Use alternate 'filename' as the configuration file. # # -h Append the $HOSTNAME to the configuration filename. # # -i Ignore creation times, check permissions, adds, deletes. # # -l Log information to logger rather than stdout messages. # # -r Report mode - so you can save to a file and get a positive # # record of what you have checked, even if errors go to logger # # -s Sign each file with a hash signature. # # -v Verbose mode, not used for report generation. # # -x eXtended Unix checks - Nlinks, UID, GID, Major/Minor numers # # # # Author: Michael A. Gumienny # # # # Written: 1996 # # # ############################################################################# ############################################################################# # $Log: fcheck,v $ # Revision 2.7.54 2000/11/07 12:33:32 root # Added "FindDiff" routine to report the detail of what has been changed, # taking all guess work out of the reported lines. # # FCheck WARNING lines now report like the following: # fcheck: "WARNING: [host] /bin/cron [HASHs: 3932278317 - 2003041699]" # which tells you immediately that there is a variation in the HASHs. # # # Revision 2.7.52Beta 2000/10/15 16:50 # Added more features, merged in several suggestions from jim moore. # (Sorry for the delay in getting this out with them.) # # The ability to include chunks of configuration files through the CFINCLUDE # directive (suggestion by John Vogtle) # # Added the "FILE" type for checking individual files. # # Made the database into a single file # # Fixed bug in MD5 handling (so that it can use standard source of MD5 # available) # # Added report mode, so that the report is sufficient for documentation # # Added modes to check uid, gid, and major and minor numbers on Unix special # files # # Added ReadDB and WriteDB configuration primatives, so that the same config # file could be used to generate a database that would be moved to read-only # a location # # Added check for users passing a (-s) flag to HASHFunc (otherwise the open # tries to execute the file and collect its output) # # # Revision 2.7.51 2000/04/02 18:45:32 root # Final checkin for revision control of tested script # # # Revision 2.7.50 2000/03/11 16:10:39 root # Modified parsing of "logger" variable to allow user defined option flags # # Finally got around to fixing the trailing space bug in the configuration # file. Now the parser is less strict of the varying editors being used to # create configuration files. # # Todo: request have been made to register a permission problem when ran # as other than root user, and can't recurse directory trees rather # than terminate with an error as fcheck does now. # # # Revision 2.7.49 2000/03/11 15:08:04 root # Fixed option when told to ignore creation dates to also check file size. # # Fixed option when told to ignore diretory names (-d), when you are not # checking recursively and don't want to see directory Inode changes. # # # Revision 2.7.47 2000/02/21 02:05:18 root # Removed the pre-defined "-t" (tag) option used by logger to allow for user # defined output devices: scritps, programs, or device files. # # Typo found under permission calculations: # local ($ftype) = $ftype[($mode & 0170000)>>12]; # # A reported glitch with European and some US spellings for filenames that # contain a single quote (D'Abo) was fixed. # # # Revision 2.7.46 2000/01/10 23:06:22 root # Minor improvements and documentation efforts made. # Replaced uneccesary date coding to compensate epoch of January 1, 1970 GMT # $year += ($year < 70) ? 2000 : 1900; # with a simpler # $year += 1900; # # Revision 2.7.45 1999/10/19 17:40:22 root # Added optional file hash/HASH signature abilities. # # Added support for multiple configuration files by passing optional name. # # Updated documentation. # # Todo: Minor issue. Need to check string parsing from configuration file. # Spaces on end of string confuse current parser logic. # # # Revision 2.7.43 1999/10/08 20:57:34 root # Added optional HASH/hash signature ability # # # Revision 2.7.42 1999/09/21 00:55:14 root # Experimental stages, checked in. # # # Revision 2.7.40 1999/09/14 02:18:01 root # Added suggestion by Ian Thurlbeck to replace the # lookup intensive arrays with associative arrays to speed things along. # # Removed a few un-needed lines and routines left in from previos edits. # # Updated documentation. # # # Revision 2.7.38 1999/08/16 22:16:37 root # Enclosed logger string in quotes. RedHat Linux logger choked without them. # Fixed spelling errors, etc in documentation. # # # Revision 2.7.37 1999/08/04 22:51:00 root # Minor changes to permissions routines. # # # Revision 2.7.34 1999/07/29 01:11:13 root # Now Works under windows, added internal ls type function. # # # Revision 2.6.27 1999/07/24 14:49:04 root # Initial checkin for migration to DOS PERL # use Digest::MD5; ############################################################################# # # # User modifiable variable definitions: # # # ############################################################################# # This should be passed through the command line, but hard coding still works $config="/usr/local/etc/fcheck/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); undef($Hash); undef($XTended); undef($Reporting); undef($ReportFail); undef($ALogger); undef($ReadDB); undef($WriteDB); $XTended=0; $Reporting=0; $ReportFail=0; ($Me)=split("/", reverse($0)); ($Me)=split(" ", reverse($Me)); $rcfflag=1; ############################################################################# # &Help; # # This routine explains brief usage syntax to STDOUT. The program is then # # terminated. # ############################################################################# sub Help { open(ME, "<$0"); for ($i=0; $i<38; $i++) { $_=; } close(ME); $_ = substr($_, 15, 18); printf("Usage:\t%s [-acdfhilrsvx] [config filename] [directory]\n", $Me); printf("\tUsed to validate creation dates of critical system files, and\n"); printf("\tas a baseline system integrity and verification script.\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-f\tUse alternate 'config filename' to initiate from.\n"); printf("\t-h\tAppend the \$HOSTNAME to the configuration filename.\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-r\tReporter mode that lists individual files too.\n"); printf("\t-s\tSign each file with a hash signature.\n"); printf("\t-v\tVerbose mode.\n"); printf("\t-x\teXtended Unix checks - Nlinks, UID, GID, Major/Minor numbers.\n\n"); exit(0); } ############################################################################# # &DoConfig($config_file,$recurse,$RecurseDepth); # ############################################################################# sub DoConfig { local($CFFile,$godeep,$CLevel)=@_; local($KeyWord, $Variable); local(*CONFIG); if ($Verbose) { printf("debug: (DoConfig) Drawing Config from %s\n", $CFFile); } open(CONFIG, "<$CFFile") || &Error("Can't find configuration file $CFFile"); $CLevel++; $CTemp="\t" x $CLevel ; $CString="$CTemp"."$CFFile"; push(@ConfigTree,$CString); while() { next if /^#/ || /^\n/; chop; ($KeyWord, $Variable)=m/(\w+)\s*=\s*(.*)/; # Fast hack to remove trailing spaces... $Variable =~ s/\s*$//; $KeyWord = &toupper($KeyWord); if ($Verbose) { printf("debug: Key:\"%s\"\tValue:\"%s\"\n", $KeyWord, $Variable); } # Allow config files to be modular, included from one another # This means that the last definition wins if ($KeyWord eq "CFINCLUDE") { if (!(-r $Variable && -s $Variable)) { &Error("CFINCLUDE $Variable in config file NOT readable or zero length"); } if (-f "$Variable" && ($godeep)) { &DoConfig("$Variable", $godeep,$CLevel); } } if ($KeyWord eq "DIRECTORY") { push(@CheckDir, $Variable); } if ($KeyWord eq "FILE") { push(@CheckFile, $Variable); } if ($KeyWord eq "EXCLUSION") { if($DOS) { $Variable = &toupper($Variable); } # If they left a trailing slash / then chop it # When files or directories come by for # Copmarison, they will have no trailing / $TVariable="$Variable"; if ($TVariable =~ m#/$#) { push(@ExcludeDir, $TVariable); } else { $ExcludeMatrix{"$TVariable"} = 1; } } if ($KeyWord eq "LOGGER") { ($Logger, $LoggerFlags) = $Variable =~ m/(\S+)\s* s*(.*)\s*/; (@LogFlags)=split(' ',$LoggerFlags); if ($Verbose) { printf("debug: Corrected LOGGER value:\"%s\"\ndebug: Corrected LOGGER FLAGs:\"%s\"\n", $Logger, $LoggerFlags); } } if ($KeyWord eq "AUTHLOGGER") { ($ALogger, $ALoggerFlags) = $Variable =~ m/(\S+)\s* s*(.*)\s*/; (@ALogFlags) = split(' ',$ALoggerFlags); if ($Verbose) { printf("debug: Corrected AUTHLOGGER value:\"%s\"\ndebug: Corrected AUTHLOGGER FLAGs:\"%s\"\n", $ALogger, $ALoggerFlags); } } if ( $KeyWord eq "FILETYPER") { $Filefunc = $Variable; } if ( $KeyWord eq "DATABASE") { $DBFile = $Variable; } if ( $KeyWord eq "READDB") { $ReadDB = $Variable; } if ( $KeyWord eq "WRITEDB") { $WriteDB = $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'}=""; $ThisOS=`uname -s -r`; chop($ThisOS); $Uname=`uname -a`; chop($Uname); } if ($KeyWord eq "TIMEZONE") { $ENV{'TZ'}=&toupper($Variable); } if ($KeyWord eq "SIGNATURE") { $HASHFunc = $Variable; } } } ############################################################################# # &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 = `uname -n`; chop($ThisHost); $ThisOS=`uname -s -r`; chop($ThisOS); $Uname=`uname -a`; chop($Uname); } } # 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"); } # Allow for a config definition involving the $HOSTNAME if ($UseHostName) { $CFInput=sprintf("%s.%s", $config, $ThisHost); } else { $CFInput=$config; } if ($Verbose) { printf("debug: (Configure) Configuration file set to: %s\n", $CFInput); } $ConfigLevel = -1; # Make an attempt to find the configuration file at least one deep &DoConfig($CFInput,$rcfflag,$ConfigLevel); if (!$ENV{'TZ'}) { printf("Error: Need TZ environment configured or environment set!\n\n"); &Help; } } ############################################################################# # &Exclude($filename); # ############################################################################# sub Exclude { local($EFile)=@_; local($Line); if ($Verbose) { printf("debug: (Exclude) checking on %s\n", $EFile); } # exact match (for file) if (defined($ExcludeMatrix{"$EFile"})) { if ($Verbose) { printf("debug: (Exclude) match found\n"); } return(1); } # regexp match (for dir) foreach $Line (@ExcludeDir) { if ($EFile =~ m#^$Line#) { if ($Verbose) { printf("debug: (Exclude) match found\n"); } return(1); } } return(0); } ############################################################################# # &BuildFileSeg(); # ############################################################################# sub BuildFileSeg { if ($Verbose) { printf("debug: (BuildFileSeg) building baseline %s\n", $DBfile); } printf(DB "# - - - - -> BEGIN FILES <- - - - -\n"); foreach $Line (@LiveData) { printf(DB "%s\n", $Line); } printf(DB "# - - - - -> END FILES <- - - - -\n"); return 0; } ############################################################################# # &BuildDirSeg($DirName,$recurse); # ############################################################################# sub BuildDirSeg { local($DirName,$recurse)=@_; if ($Verbose) { printf("debug: (BuildDirSeg) building baseline %s\n", $DBfile); } if ( $recurse == 1 ) { printf(DB "# - - - - -> BEGIN Directory Recursion %s <- - - - -\n", $DirName); } else { printf(DB "# - - - - -> BEGIN Directory %s <- - - - -\n", $DirName); } foreach $Line (@LiveData) { printf(DB "%s\n", $Line); } return 0; } ############################################################################# # &GetFilesDB($dir); # # This routine builds the array @BaseLineData from the database segment # ############################################################################# sub GetFilesDB { local($index,$BI,$BP,$BL,$BU,$BG,$BS,$BT,$BN,$HASH,$pdir); if ($DOS) { $Dirname =~ s/:/.drive/; } # Handle drive delimiters, C:, D:, E:, etc. %BaseLineData = (); if (($FilesBegin != -1) && ($FilesEnd != -1) && ($FilesEnd <= $FilesEnd)) { for ($index=$FilesBegin; $index<$FilesEnd + 1; $index++) { $_="$Database[$index]"; chop; if($Verbose) { printf("debug: (GetFilesDB) reading [%s]\n", $_); } # BI - Baseline Inode, BP - Baseline Permissions, BS - Size # BT - Time, BN - Name (all generated by the "stat" call of perl # B_HASH - the Checksum/Signature # eXtended format has $BL - Link Count, $BU - Uid, $BG - Gid if ($XTended) { ($BI, $BP, $BL, $BU, $BG, $BS, $BT, $BN, $HASH) = split("!", $_); } else { ($BI, $BP, $BS, $BT, $BN, $HASH) = split("!", $_); } if(((($BP & 0170000)>>12)==4) ) { next; } $BaseLineData{$BN} = $_; } } else { if ($Verbose) { printf("debug: (GetFilesDB) No files to retrieve from DB"); } } return(@BaseLineData); } ############################################################################# # &GetLiveFiles(); # # This routine builds the array @LiveData based on the live data. # ############################################################################# sub GetLiveFiles { local($filename); if ($Verbose) { printf("debug: (GetLiveFiles) reading %s files\n",$#CheckFile ); } if ($#CheckFile != -1 ) { for ($index=0; $index < $#CheckFile + 1; $index++) { if ($Verbose) { printf("debug: (GetLiveFiles) reading %s \n",$CheckFile[$index]); } # Report configuration problems - non-existant files - when Baselining if (!-e "$CheckFile[$index]" && $BaseLine) { if ($Logging) { if(!$DOS) { $cmd=sprintf("\"CONFIG: File %s on %s contains reference to non-existant file %s\"\n", $config,$ThisHost,$CheckFile[$index]); system($Logger, @LogFlags, $cmd); } } if ((!$Logging) || (($Logging) && ($Reporting))) { printf("\nCONFIG: File %s on %s contains reference to non-existant file %s\n",$config,$ThisHost,$CheckFile[$index]); } } # Root generally can not read a file if it is a link and the link points to # a non-existant file. Basically the stat will fail next if (!-r "$CheckFile[$index]" ); if (-d "$CheckFile[$index]" ) { push(@CheckDir, "$CheckFile[$index]" ); if ($Logging) { $cmd=sprintf("\"CONFIG: Configuration file on %s labels %s as a file when it is a directory\"\n",$ThisHost,$CheckFile[$index]); system($Logger, @LogFlags, $cmd); } next; } elsif (-f "$CheckFile[$index]" ) { &GetFileInfo("$CheckFile[$index]"); } elsif (!$DOS) { # give the Unix folks a bit more info if (-p "$CheckFile[$index]" || -S "$CheckFile[$index]" || -b "$CheckFile[$index]" || -c "$CheckFile[$index]") { &GetFileInfo("$CheckFile[$index]"); } } else { if ($Logging) { $cmd=sprintf("\"CONFIG: Configuration file on %s labels %s as a file when it is not a file or a directory\"\n",$ThisHost,$CheckFile[$index]); system($Logger, @LogFlags, $cmd); } next; } } } else { if ($Verbose) { printf("debug: (GetLiveFiles) No individual files to read\n"); } } } ############################################################################# # &GetDirDB($dir); # # This routine builds the array @BaseLineData from the database segment # ############################################################################# sub GetDirDB { local($Dirname)=@_; local($index,$BI,$BP,$BL,$BU,$BG,$BS,$BT,$BN,$HASH); if ($DOS) { $Dirname =~ s/:/.drive/; } # Handle drive delimiters, C:, D:, E:, etc. $Dirname =~ s/\//_/g; if ($Verbose) { printf("debug: (GetDirDB) reading \n", $Dirname); } %BaseLineData = (); if ((defined($begin{"$Dirname"})) && (defined($end{"$Dirname"})) && ($begin{"$Dirname"} <= $end{"$Dirname"})) { for ($index=$begin{"$Dirname"}; $index<$end{"$Dirname"} + 1; $index++) { $_="$Database[$index]"; chop; if($Verbose) { printf("debug: (GetDirDB) reading [%s]\n", $_); } # BI - Baseline Inode, BP - Baseline Permissions, BS - Size # BT - Time, BN - Name (all generated by the "stat" call of perl # B_HASH - the Checksum/Signature if ($XTended) { ($BI, $BP, $BL, $BU, $BG, $BS, $BT, $BN, $HASH) = split("!", $_); } else { ($BI, $BP, $BS, $BT, $BN, $HASH) = split("!", $_); } if(!$DirCheck) { next if ( $BP =~ /^d/); } if( (!$DirCheck) && (!$DOS) && ((($BP & 0170000)>>12)==4) ) { next; } $BaseLineData{$BN} = $_; } } else { if (($Logging) && ($BaseLine)) { if(!$DOS) { $cmd=sprintf("\"CONFIG: %s database %s does NOT match config file %s on %s for %s\"\n", $Me,$DBFile,$config,$ThisHost,$Dirname); system($Logger, @LogFlags, $cmd); } } &Error("Error: Baseline does not match configuration file on $Dirname"); } return(@BaseLineData); } ############################################################################# # &GetLiveDir($dir); # # This routine builds the array @LiveData based on the live data. # ############################################################################# sub GetLiveDir { local($Dir)=@_; local($filename); if ($Verbose) { printf("debug: (GetLiveDir) reading %s\n", $Dir); } if (! -d $Dir ) { if ($Logging) { if (!$DOS) { $cmd=sprintf("\"CONFIG: Configuration file %s on %s declares %s a Directory when it is not\"\n", $config,$ThisHost,$Dir); system($Logger, @LogFlags, $cmd); } } else { printf("\nCONFIG: Configuration file %s on %s labels %s as a directory when it is NOT \n",$config,$ThisHost,$Dir); } return; } else { # It's a directory if ($Dir =~ /:\/$/) { &GetDir($Dir, 0); } elsif (($Dir =~ /\/$/) && ($Dir ne "/")) { $Dir =~ s/\/$//; &GetDir($Dir, 1); } else { &GetDir($Dir, 0); } } } ############################################################################### # $x=&Scan_Build(type); # # This is the heart of the script. Scan_Build will either scan and/or build a # # grouping of files based on the files or directories given it in the config # # file works on a directory by directory basis or a list of individual files # ############################################################################### sub Scan_Build { local($type)=@_; local($Hacks); # Check the type and log what we are doing verbose / debugging if (("$type" eq "Files") && ($Verbose)) { printf("debug: (Scan_Build) reading Files \n"); } elsif (("$type" eq "Directory") && ($Verbose)) { printf("debug: (Scan_Build) reading directory %s\n", $Dir); } elsif (("$type" ne "Files") && ("$type" ne "Directory")) { &Error("Error: Scan_Build received an invalid type $type -- Must be Files or Directory"); } # Hacks is where plusses, minuses, and changes get incremented $Hacks=0; # If we are not building, then we are scanning if (!$BaseLine) { if (("$type" eq "Files")) { # Scan the individual files # Load the segment of an existing database into an associative array BaseLineData &GetFilesDB(); # Build a similar array from the directory as it exists now &GetLiveFiles(); } else { # Scan directory # Load the segment of an existing database into an associative array BaseLineData &GetDirDB($Dir); # Build a similar array from the directory as it exists now &GetLiveDir($Dir); } # Loop over the live data to detect differences for ($ldd=0; $ldd < $#LiveData + 1; $ldd++) { if ($XTended) { ($L_Inode, $L_Perms, $L_Links, $L_Uid, $L_Gid, $L_Size, $L_Time, $L_Name, $L_HASH) = split("!", $LiveData[$ldd]); } else { ($L_Inode, $L_Perms, $L_Size, $L_Time, $L_Name, $L_HASH) = split("!", $LiveData[$ldd]); } # Ignore any not in BaseLineData next unless defined($BaseLineData{$L_Name}); if ($XTended) { ($B_Inode, $B_Perms, $B_Links, $B_Uid, $B_Gid, $B_Size, $B_Time, $B_Name, $B_HASH) = split("!", $BaseLineData{$L_Name}); } else { ($B_Inode, $B_Perms, $B_Size, $B_Time, $B_Name, $B_HASH) = split("!", $BaseLineData{$L_Name}); } # In the next version the checks, and the logging will be built through a mask if ($CreateDate) { # Ignore creation dates # Check to see if there is a mismatch in either the permissions or inode number if ((!$Hash) && (($L_Perms ne $B_Perms) || ($L_Inode ne $B_Inode) || ($L_Size ne $B_Size))) { # Are we logging (Unix style logger command) if ($Logging) { # Form the command in the $cmd variable $cmd = sprintf("\"WARNING: [%s] %s [%s]\"\n", $ThisHost, $L_Name, &FindDiff($B_Inode, &ShowPerms($B_Perms), $B_Size, &ctime($B_Time), "UID", "GID", "LINKS", "HASH", $B_Name, $L_Inode, &ShowPerms($L_Perms),$L_Size, &ctime($L_Time), "UID", "GID", "LINKS", "HASH"), $L_Name); system($Logger, @LogFlags, $cmd); } if ((!$Logging) || (($Logging) && ($Reporting))) { # Logger not available - print to screen printf("\nWARNING: [%s] %s\n[%s]\n", $ThisHost, $L_Name, &FindDiff($B_Inode, &ShowPerms($B_Perms), $B_Size, &ctime($B_Time), "UID", "GID", "LINKS", "HASH", $B_Name, $L_Inode, &ShowPerms($L_Perms),$L_Size, &ctime($L_Time), "UID", "GID", "LINKS", "HASH"), $L_Name); } # Increment the number of "Hacks" for mismatched permissions or inode numbers ++$Hacks; } # Additionally check for Hash (e.g. MD5) mismatches # Do we care elsif (($Hash) && (($L_HASH ne $B_HASH) || ($L_Perms ne $B_Perms) || ($L_Inode ne $B_Inode) || ($L_Size ne $B_Size))) { if ($Logging) { # Show the change to HASH/Signature $cmd = sprintf("\"WARNING: [%s] %s [%s]\"\n", $ThisHost, $L_Name, &FindDiff($B_Inode, &ShowPerms($B_Perms),$B_Size, &ctime($B_Time), "UID", "GID", "LINKS", $B_HASH, $B_Name, $L_Inode, &ShowPerms($L_Perms),$L_Size, &ctime($L_Time), "UID", "GID", "LINKS", $L_HASH), $L_Name); system($Logger, @LogFlags, $cmd); } if ((!$Logging) || (($Logging) && ($Reporting))) { printf("\nWARNING: [%s] %s\n[%s]\n", $ThisHost, $L_Name, &FindDiff($B_Inode, &ShowPerms($B_Perms), $B_Size, &ctime($B_Time), "UID", "GID", "LINKS", $B_HASH, $B_Name, $L_Inode, &ShowPerms($L_Perms),$L_Size, &ctime($L_Time), "UID", "GID", "LINKS", $L_HASH), $L_Name); } # Increment the number of "Hacks" for mismatched hashes ++$Hacks; } elsif ($Reporting) { if ( ("$type" eq "Files") || (-d $L_Name)) { printf("No changes on %s to: %s \n", $ThisHost, $L_Name); } } } elsif ($XTended) { # Creation time mode - Check times & permissions & inodes if ((!$Hash) && (($L_Time ne $B_Time) || ($L_Size ne $B_Size) || ($L_Perms ne $B_Perms) || ($L_Inode ne $B_Inode) || ($L_Links ne $B_Links) || ($L_Uid ne $B_Uid) || ($L_Gid ne $B_Gid) )) { # Log differences if($Logging) { $cmd = sprintf("\"WARNING: [%s] %s [%s]\"\n", $ThisHost, $L_Name, &FindDiff($B_Inode, &ShowPerms($B_Perms),$B_Size, &ctime($B_Time), $B_Uid, $B_Gid, $B_Links, $B_HASH, $B_Name, $L_Inode, &ShowPerms($L_Perms),$L_Size, &ctime($L_Time), $L_Uid, $L_Gid, $L_Links, $L_HASH), $L_Name); system($Logger, @LogFlags, $cmd); } if ((!$Logging) || (($Logging) && ($Reporting))) { printf("\nWARNING: [%s] %s\n[%s]\n", $ThisHost, $L_Name, &FindDiff($B_Inode, &ShowPerms($B_Perms), $B_Size, &ctime($B_Time), $B_Uid, $B_Gid, $B_Links, $B_HASH, $B_Name, $L_Inode, &ShowPerms($L_Perms),$L_Size, &ctime($L_Time), $L_Uid, $L_Gid, $L_Links, $L_HASH), $L_Name); } # Increment # of changes ++$Hacks; } # Check hashes in addition to times & permissions & inodes elsif (($Hash) && (($L_Time ne $B_Time) || ($L_Size ne $B_Size) || ($L_Perms ne $B_Perms) || ($L_Inode ne $B_Inode) || ($L_Links ne $B_Links) || ($L_Uid ne $B_Uid) || ($L_Gid ne $B_Gid) || ($L_HASH ne $B_HASH))) { if($Logging) { # Show the change to HASH/Signature $cmd = sprintf("\"WARNING: [%s] %s [%s]\"\n", $ThisHost, $L_Name, &FindDiff($B_Inode, &ShowPerms($B_Perms),$B_Size, &ctime($B_Time), $B_Uid, $B_Gid, "LINKS", $B_HASH, $B_Name, $L_Inode, &ShowPerms($L_Perms),$L_Size, &ctime($L_Time), $L_Uid, $L_Gid, "LINKS", $L_HASH), $L_Name); system($Logger, @LogFlags, $cmd); } if ((!$Logging) || (($Logging) && ($Reporting))) { printf("\nWARNING: [%s] %s\n[%s]\n", $ThisHost, $L_Name, &FindDiff($B_Inode, &ShowPerms($B_Perms), $B_Size, &ctime($B_Time), $B_Uid, $B_Gid, "LINKS", $B_HASH, $B_Name, $L_Inode, &ShowPerms($L_Perms),$L_Size, &ctime($L_Time), $L_Uid, $L_Gid, "LINKS", $L_HASH), $L_Name); } # End Logging # Increment # of changes ++$Hacks; } # End Hash check elsif ($Reporting) { if ( ("$type" eq "Files") || (-d $L_Name)) { printf("No changes on %s to: %s \n", $ThisHost, $L_Name); } } } else { # Creation time mode - Check times & permissions & inodes if ((!$Hash) && (($L_Time ne $B_Time) || ($L_Size ne $B_Size) || ($L_Perms ne $B_Perms) || ($L_Inode ne $B_Inode) )) { # Log differences if($Logging) { $cmd = sprintf("\"WARNING: [%s] %s [%s]\"\n", $ThisHost, $L_Name, &FindDiff($B_Inode, &ShowPerms($B_Perms),$B_Size, &ctime($B_Time), "UID", "GID", "LINKS", "HASH", $B_Name, $L_Inode, &ShowPerms($L_Perms),$L_Size, &ctime($L_Time), "UID", "GID", "LINKS", "HASH"), $L_Name); system($Logger, @LogFlags, $cmd); } if ((!$Logging) || (($Logging) && ($Reporting))) { printf("\nWARNING: [%s] %s\n[%s]\n", $ThisHost, $L_Name, &FindDiff($B_Inode, &ShowPerms($B_Perms), $B_Size, &ctime($B_Time), "UID", "GID", "LINKS", "HASH", $B_Name, $L_Inode, &ShowPerms($L_Perms),$L_Size, &ctime($L_Time), "UID", "GID", "LINKS", "HASH"), $L_Name); } # Increment # of changes ++$Hacks; } # Check hashes in addition to times & permissions & inodes elsif (($Hash) && (($L_Time ne $B_Time) || ($L_Size ne $B_Size) || ($L_Perms ne $B_Perms) || ($L_Inode ne $B_Inode) || ($L_HASH ne $B_HASH))) { if ($Logging) { # Change to HASH/Signature $cmd = sprintf("\"WARNING: [%s] %s [%s]\"\n", $ThisHost, $L_Name, &FindDiff($B_Inode, &ShowPerms($B_Perms),$B_Size, &ctime($B_Time), "UID", "GID", "LINKS", $B_HASH, $B_Name, $L_Inode, &ShowPerms($L_Perms),$L_Size, &ctime($L_Time), "UID", "GID", "LINKS", $L_HASH), $L_Name); system($Logger, @LogFlags, $cmd); } if ((!$Logging) || (($Logging) && ($Reporting))) { printf("\nWARNING: [%s] %s\n[%s]\n", $ThisHost, $L_Name, &FindDiff($B_Inode, &ShowPerms($B_Perms), $B_Size, &ctime($B_Time), "UID", "GID", "LINKS", $B_HASH, $B_Name, $L_Inode, &ShowPerms($L_Perms),$L_Size, &ctime($L_Time), "UID", "GID", "LINKS", $L_HASH), $L_Name); } # Increment # of changes ++$Hacks; } elsif ($Reporting) { if (("$type" eq "Files") || (-d $L_Name)) { printf("No changes on %s to: %s \n", $ThisHost, $L_Name); } } } # Delete matches in BaseLineData and LiveData so that the values # that remain are added or deleted files $BaseLineData{$L_Name} = ""; $LiveData[$ldd] = ""; } # loop over remaining LiveData elements (ones matching entries in # BaseLineData set to empty string) # Check for files that have been added foreach $Live (@LiveData) { next if ($Live eq ""); if ($XTended) { ($Inode, $Perms, $NLinks, $Uid, $Gid, $Size, $Time, $Name, $HASH) = split("!", $Live);} else { ($Inode, $Perms, $Size, $Time, $Name, $HASH) = split("!", $Live); } if ($Logging) { if ($XTended) { $cmd=sprintf("\"ADDITION: [%s] %s [%s %s %s %s %s %s %s %s]\"\n", $ThisHost, $Name, $Inode, &ShowPerms($Perms), $NLinks, $Uid, $Gid, $Size, &ctime($Time)); } else { $cmd=sprintf("\"ADDITION: [%s] %s [%s %s %s %s]\"\n", $ThisHost, $Name, $Inode, &ShowPerms($Perms), $Size, &ctime($Time)); } system($Logger, @LogFlags, $cmd); } if ((!$Logging) || (($Logging) && ($Reporting))) { if ($XTended) { printf("\nADDITION: [%s] %s\n", $ThisHost, $Name); printf("Inode\tPermissons\tNLink\tUid\tGid\tSize\tCreated On\n"); printf("%s\t%s\t%s\t%s\t%s\t%s\t%s\n", $Inode, &ShowPerms($Perms), $NLinks, $Uid, $Gid, $Size, &ctime($Time)); } else { printf("\nADDITION: [%s] %s\n", $ThisHost, $Name); printf("Inode\tPermissons\tSize\tCreated On\n"); printf("%s\t%s\t%s\t%s\n", $Inode, &ShowPerms($Perms), $Size, &ctime($Time)); } } ++$Hacks; } # Check for files that have been deleted foreach $Base (sort values %BaseLineData) { next if ($Base eq ""); if ($XTended) { ($Inode, $Perms, $NLinks, $Uid, $Gid, $Size, $Time, $Name, $HASH) = split("!", $Base); } else { ($Inode, $Perms, $Size, $Time, $Name, $HASH) = split("!", $Base); } if ($Logging) { if ($XTended) { $cmd=sprintf("\"DELETION: [%s] %s [%s %s %s %s %s %s %s]\"\n", $ThisHost, $Name, $Inode, &ShowPerms($Perms), $NLinks, $Uid, $Gid, $Size, &ctime($Time)); } else { $cmd=sprintf("\"DELETION: [%s] %s [%s %s %s %s]\"\n", $ThisHost, $Name, $Inode, &ShowPerms($Perms), $Size, &ctime($Time)); } system($Logger, @LogFlags, $cmd); } if ((!$Logging) || (($Logging) && ($Reporting))) { if ($XTended) { printf("\nDELETION: [%s] %s\n", $ThisHost, $Name); printf("Inode\tPermissons\tNLink\tUid\tGid\tSize\tCreated On\n"); printf("%s\t%s\t%s\t%s\t%s\t%s\t%s\n", $Inode, &ShowPerms($Perms), $NLinks, $Uid, $Gid, $Size, &ctime($Time)); } else { printf("\nDELETION: [%s] %s\n", $ThisHost, $Name); printf("Inode\tPermissons\tSize\tCreated On\n"); printf("%s\t%s\t%s\t%s\n", $Inode, &ShowPerms($Perms), $Size, &ctime($Time)); } } ++$Hacks; } # Tally failures, compare against 0 to set the return $ReportFail += $Hacks; } else #Build baseline database { if (("$type" eq "Files")) { # Build a similar array from the directory as it exists now &GetLiveFiles(); &BuildFileSeg(); } else { # Build a similar array from the directory as it exists now &GetLiveDir($Dir); &BuildDirSeg($Dir); } undef(@BaseLineData); undef(@LiveData); return(1); } return($Hacks); } ############################################################################### # &SpecialFileProps($FileName); # # This routine will get the properties of *nix special files, beginning with # # block and character special files # ############################################################################### sub SpecialFileProps { local ($filename,$Rdev)=@_; local ($properties,$sfptmp,$majmin,$ai,@scratchpad); if ((-b "$filename" ) || (-c "$filename" )) { $majmin=$Rdev; if (defined($Filefunc)) { if ( !open(FF, "$Filefunc '$filename' |") ) { printf("Unable to read $Filefunc or $filename for generation of major/minor number.\n"); $majmin; return; } $sfptmp = ; # Chop the off the end, do I trust the chop # depends on the OS... $sfptmp =~ s/\n$//; (@scratchpad) = split(' ',$sfptmp); # it is the one after special, sans the parens SCRATCH: for ($ai=0; $ai < $#scratchpad + 1; $ai++) { if ( $scratchpad[$ai] =~ m/special/i ) { $majmin = $scratchpad[$ai + 1]; $majmin = substr($majmin,1,length($majmin) -2); last SCRATCH; } } } # If Filefunc not defined, just return rdev $properties = $majmin; } # Return properties $properties; } ############################################################################### # &GetFileInfo($FileName); # # This routine will build the @LiveData array from the information # # it is the guts of the old GetDir, so that individual files can be checked # ############################################################################### sub GetFileInfo { local($filename,@junk)=@_; local($Dev,$Inode,$Perms,$NLink,$Uid,$Gid,$Rdev,$Size,$ATime,$MTime,$CTime,$BlkSize,$Blocks); local($majmin); if ($Verbose) { printf("debug: (GetFileInfo) processing %s \n",$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.) Fixed Y2K glitch under DOS next if (&Exclude($filename)); ($Dev,$Inode,$Perms,$NLink,$Uid,$Gid,$Rdev,$Size,$ATime,$MTime,$CTime,$BlkSize,$Blocks) = stat("$filename"); # Handle links and special files a little bit differently if ($DOS) { if($Hash && !-d "$filename" && !-l "$filename") { if (!open(IN, $filename)) { printf("Unable to read $filename for signature generation.\n"); exit(2); } binmode(IN); $filesig = Digest::MD5->new->addfile(*IN)->hexdigest; close(IN); } else { $filesig = "NOHASH"; } # Do not check Directories unless "-d" was specified # if (((($Perms & 0170000) >> 12) != 4) || $DirCheck) { # DOS names are really all uppercase, so... if ($DOS) { push(@LiveData, join("!", " ", " ", $Size, $CTime, &toupper($filename), $filesig )); } else { push(@LiveData, join("!", $Inode, $Perms, $Size, $CTime, $filename, $filesig)); } if (($Verbose) && (!$DOS)) { printf("debug: (GetFileInfo) %s %s %s %s %s %s %s\n", $Inode, $Perms, $Month, $Day, $Time, $filename, $filesig); } if (($Verbose) && ($DOS)) { printf("debug: (GetFileInfo) %s %s %s %s\n", $Size, $Time, $filename, $filesig); } } } else { if ($Hash) { # Eliminate things that we don't want to "open" for reading if (-p "$filename" ) { $filesig = "FIFO"; } elsif (-S "$filename" ) { $filesig = "SOCKET"; } elsif (-b "$filename" ) { $majmin = &SpecialFileProps($filename,$Rdev); $filesig = "BlockSpecial".':'.$majmin ; } elsif (-c "$filename" ) { $majmin = &SpecialFileProps($filename,$Rdev); $filesig = "CharSpecial".':'.$majmin; } elsif (-d "$filename" ) { $filesig = "Dir"; } elsif (-f "$filename" && !-s "$filename" ) { $filesig = "ZeroLength"; } elsif (!-e "$filename" ) { $filesig = "NoSuchFile"; } # OK now start looking at signatures elsif ((-f "$filename" || -l "$filename") && -s "$filename") { if (!open(IN, $filename)) { printf("Unable to read $filename for signature generation.\n"); exit(2); } binmode(IN); $filesig = Digest::MD5->new->addfile(*IN)->hexdigest; close(IN); if (-l "$filename") { $filesig="SYMLINK:"."$filesig"; } } else { $filesig = "NOHASH"; } } else { $filesig = "NOHASH"; } # DOS names are really all uppercase, so... if($DOS) { push(@LiveData, join("!", " ", " ", $Size, $CTime, &toupper($filename), $filesig )); } else { # Match ls -li format if($XTended) { push(@LiveData, join("!", $Inode, $Perms, $NLink, $Uid, $Gid, $Size, $CTime, $filename, $filesig)); } else { push(@LiveData, join("!", $Inode, $Perms, $Size, $CTime, $filename, $filesig)); } } if (($Verbose) && (!$DOS)) { if($XTended) { printf("debug: (GetFileInfo) %s %s %s %s %s %s %s %s\n", $Inode, $Perms, $NLink, $Uid, $Gid, $CTime, $filename, $filesig); } else { printf("debug: (GetFileInfo) %s %s %s %s %s\n", $Inode, $Perms, $CTime, $filename, $filesig); } } if (($Verbose) && ($DOS)) { printf("debug: (GetFileInfo) %s %s %s %s\n", $Size, $CTime, $filename, $filesig); } } } ############################################################################### # &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)=@_; local($filename); # Is it a directory? if (-d "$rootdir") { opendir(DIR, $rootdir) || die "debug: (GetDir) No can do ($rootdir)...\n"; foreach (sort readdir(DIR)) { next if (/^\.\.?$/); $filename = $_; $filename = "$rootdir/$filename"; # Root generally can not read a file if it is a link and the link points to # a non-existant file. Basically the stat will fail next if (!-r "$filename" ); # DOS chokes when the root directory gets a double slash prepended if (!-d "$filename" || (-d "$filename" && $DirCheck )) {&GetFileInfo("$filename");} if ((-d "$filename" && !-l "$filename") && ($r)) { &GetDir("$filename", 1); } } close(DIR); } else { &GetFileInfo("$rootdir"); } } ############################################################################### # $success=&BuildDBIndex(); # # This small support routine will return the $string in uppercase format. # ############################################################################### sub BuildDBIndex { # Data format so that it is both human readible and is all in one file # and accomodates individual file # Format # First Line: # - Host *hostname* # Second Line: # - OS *OS* # Third Line: # - Database Creation *date* # Fourth Line is for *IX and is the results of "uname -a" # Fourth Line: # - Uname *uname* # # Individual files are at the Beginning with the following delimiters # - - - - -> BEGIN FILES < - - - - - # - - - - -> END FILES < - - - - - # # Directories boundaries are delimited by # - - - - -> BEGIN Directory *directory_name* < - - - - - # or # - - - - -> BEGIN Directory Recursion *directory_name* < - - - - - # # Recursion is only documented, so that the if the config file changes # # The last line in the file should be # - - - - -> END Directories < - - - - - # local($index); if ($Verbose) { printf("debug: (BuildDBIndex) processing %s \n",$filename); } $DirFromDB = "initial"; $FoundEndDir = 2 ; $FoundEndFile = 2 ; $FilesBegin = -1; $FilesEnd = -1; for ($index=0; ($Done == 0) && ($index<$#Database + 1); $index++) { # Skip header info next if ($Database[$index] =~ m/^#(-|\s|>)*Database\s*Creation\s*/) ; next if ($Database[$index] =~ m/^#(-|\s|>)*Host\s*/) ; next if ($Database[$index] =~ m/^#(-|\s|>)*OS\s*/) ; next if ($Database[$index] =~ m/^#(-|\s|>)*Uname\s*/) ; if ($Database[$index] =~ m/^#(-|\s|>)*BEGIN\s*FILES\s*/) { $FoundEndFile = 0 ; if ($Verbose) { printf("debug: (BuildDBIndex) Individual Files declaration at line %s\n",$index); } # Define the elements into the "files" array $FilesBegin = $index + 1; } elsif ($Database[$index] =~ m/^#(-|\s|>)*END\s*FILES\s*/) { $FilesEnd = $index - 1 ; $FoundEndFile = 1 ; # Begin processing directories or files } elsif ($Database[$index] =~ m/^#(-|\s|>)*BEGIN\s*Directory\s*/) { # Detect delimiter problems if ($FoundEndFile == 0) { &Error("Malformed database no ending delimiter line for FILES" ); } # Store last line of previous directory if ($DirFromDB ne "initial") { $end{"$DirFromDB"} = $index - 1 }; $FoundEndDir = 0 ; $tmp = "$Database[$index]"; chop($tmp); # Chop up the line $tmp =~ s/^#(-|\s|>)*BEGIN\s*Directory\s*// ; if ($tmp =~ m/^\s*Recursion\s*/) { $tmp2 = "$tmp"; $tmp2 =~ s/^\s*Recursion\s*//; $tmp2 =~ s/(-|\s|>|<)*$//; # From original fcheck - DOS may have heartburn $tmp2 =~ s/\//_/g; $DirFromDB = "$tmp2"; $recurse["$DirFromDB"] = 1; if ($Verbose) { printf("debug: (BuildDBIndex) Recursive Directory declaration for %s at line %s \n",$DirFromDB,$index); } } else { $tmp =~ s/(-|\s|>|<)*$//; # From original fcheck - DOS may have heartburn $tmp =~ s/\//_/g; $DirFromDB = "$tmp"; if ($Verbose) { printf("debug: (BuildDBIndex) Directory declaration for %s at line %s \n",$DirFromDB,$index); } } $begin{"$DirFromDB"} = $index + 1; } elsif ($Database[$index] =~ m/^#(-|\s|>)*END\s*Directories\s*/) { if ($DirFromDB ne "initial") { $end{"$DirFromDB"} = $index - 1 }; $FoundEndDir = 1 ; } } # Detect delimiter problems if ($FoundEndDir == 0) { &Error("Malformed database no ending delimiter line for Directories" ); } } ############################################################################### # $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... # Will anybody still be using this after 2036? #$year += ($year < 70) ? 2000 : 1900; $year += 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)); } ############################################################################### # &Error("string"); # # This routine prints out critical errors and terminates execution. # ############################################################################### sub Error { printf("%s: %s\nterminating...\n\n", $Me, @_); exit(2); } ############################################################################### # $x=&FindDiff($LInode, $LPerms, $LSize, $LTime, $Luid, $Lgid, $LLinks, $LHASH,# # $CInode, $CPerms, $CSize, $CTime, $Cuid, $Cgid], $CLinks, $CHASH); # # This routine will determine the differences between the baseline database # # and the current run, and return only thoses differences. # ############################################################################### sub FindDiff { local($C); local($LI, $LP, $LS, $LT, $LU, $LG, $LL, $LC, $CN, $CI, $CP, $CS, $CT, $CU, $CG, $CL, $CC)=@_; if ($LI ne $CI) { $C = sprintf("Inodes: %s - %s, ", $LI, $CI); } if ($LP ne $CP) { $C = sprintf("%sPermissions: %s - %s, ", $C, $LP, $CP); } if ($LS ne $CS) { $C = sprintf("%sSizes: %s - %s, ", $C, $LS, $CS); } if ($LT ne $CT) { $C = sprintf("%sTimes: %s - %s, ", $C, $LT, $CT); } if ($LU ne $CU) { $C = sprintf("%sUIDs: %s - %s, ", $C, $LU, $CU); } if ($LG ne $CG) { $C = sprintf("%sGIDs: %s - %s, ", $C, $LG, $CG); } if ($LL ne $CL) { $C = sprintf("%sLinks: %s - %s, ", $C, $LL, $CL); } if ($LC ne $CC) { $C = sprintf("%sHASHs: %s - %s, ", $C, $LC, $CC); } #printf("WARNING: %s\n", $C); # $cmd = sprintf("\"WARNING: [%s] %s [%s]\"\n", $ThisHost, $L_Name, # &FindDiff($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($Logger, @LogFlags, $cmd); # Quick hack to get this released on time. chop($C); chop($C); return($C); } ############################################################################### # 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 ($Verbose) { printf("debug: processing command line - arg %s \t ARGV[0] %s\n",$arg,$ARGV[0]); } if ($arg =~ /^-/) { if ($arg =~ /a/) { $Auto=1; } if ($arg =~ /c/) { $BaseLine=1; } if ($arg =~ /d/) { $DirCheck=1; } if ($arg =~ /f/) { $Config=1; } if ($arg =~ /i/) { $CreateDate=1; } if ($arg =~ /h/) { $UseHostName=1; } if ($arg =~ /l/) { $Logging=1; } if ($arg =~ /r/) { $Reporting=1; } if ($arg =~ /s/) { $Hash=1; } if ($arg =~ /v/) { $Verbose=1; } if ($arg =~ /x/) { $XTended=1; } } } shift(@ARGV); if ($Config) { $config = shift(@ARGV); } $Dir = shift(@ARGV); } # Give user syntax help if (($Dir eq "") && (!$Auto)) { &Help; } $Dir =~ s/\\/\//g; if ($BaseLine) { $DirCheck=1; } &Configure; # Make sure that you have a HASHFunc if you request the hash, otherwise the # open will try to execute the filename if($Hash) { if (!(-s $HASHFunc && -x $HASHFunc)) { &Error("Signaturing requested with -s switch, but no SIGNATURE program specified in config file"); } } # Allow for a higher security mode, where databases are written to one place # and read from another, with the same config file # Sanity check is if both are defined if (defined($ReadDB) && defined($WriteDB)) { #Only work with the automatic modes if (($BaseLine) && ($Auto)) { $DBFile = "$WriteDB"; } elsif ((!$BaseLine) && ($Auto)) { $DBFile = "$ReadDB"; } } #If a separate Authorization logger is not defined, try to approximate one if (($Logging) && (!$DOS) && (!defined($ALogger))) { #Push towards the auth log if this hasn't been defined #If you don't like it use the keyword and define it yourself $ALogger="${Logger}"; $ALoggerFlags="${LoggerFlags}"; foreach $atom (@LogFlags) { if ($atom =~ m/-p auth/) { $atom =~ s/[a-z0-7]*/auth/; } push(@ALogFlags,$atom); } } elsif (($Logging) && (!defined($ALogger))) { #Mimic normal logger $ALogger="${Logger}"; $ALoggerFlags="${LoggerFlags}"; (@ALogFlags)=split(' ',$ALoggerFlags); } if ($Verbose) { printf("debug: Processing host [%s]\n", $ThisHost); } # Handling only one database file now if ($BaseLine) { # Open for write open (DB, ">$DBFile") || &Error("no fcheck database exists! [$DBFile]"); printf(DB "# - Host %s\n",$ThisHost); printf(DB "# - OS %s\n",$ThisOS); $#junk = -1; (@junk) = stat("$DBFile"); $DBCTime = $junk[10]; $#junk = -1; printf(DB "# - Database Creation %s\n",&ctime($DBCTime)); if (!$DOS) { printf(DB "# - Uname %s\n",$Uname); } } else { #Open for read open (DB, "<$DBFile") || &Error("no fcheck database exists! [$DBFile]"); #Suck the database into an array @Database=; close(DB); &BuildDBIndex } # Building a baseline is a significant event - syslog it if you can if (($Logging) && ($BaseLine)) { if(!$DOS) { # All rebuilds are significant $cmd=sprintf("\"INFO: Rebuild of the %s database %s begun for %s using config file %s\"\n",$Me,$DBFile,$ThisHost,$config); system($ALogger, @ALogFlags, $cmd); } } $Processing=""; if ($Auto) { if ($Reporting) { printf("Configuration: Configuration on %s begins with %s \n", $ThisHost,$config); for ($cti=0; $cti < $#ConfigTree + 1; $cti++) { printf("%s\n", $ConfigTree[$cti]); } printf("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n"); } # Processing files and Directories except files are treated like one directory # Make sure we have files to process if ($#CheckFile != -1 ) { $Processing="Files"; if (!$BaseLine) { if (!$Logging) { if ($Verbose) { printf("\nPROGRESS: validating integrity of Files\nSTATUS: "); } } } if ($Verbose) { printf("\nbuilding baseline for Files\n"); } if ((!&Scan_Build($Processing)) && (!$Logging)) { if ($Verbose) { printf("passed...\n\n"); } } undef(@BaseLineData); undef(@LiveData); } else # No FILE directives in config file { if ((!$BaseLine) && (!$Logging)) { if ($Verbose) { printf("\nPROGRESS: No individual files to validating \n"); } } if ($Verbose) { printf("\nNo Files specified for baseline, check for directories\n"); } } # Make sure we have directories to process if ($#CheckDir != -1 ) { $Processing="Directory"; foreach $Dir (@CheckDir) { if (!$BaseLine) { if (!$Logging) { if ($Verbose) { printf("\nPROGRESS: validating integrity of %s\nSTATUS:", $Dir); } } } if ($Verbose) { printf("\nbuilding baseline for %s\n", $Dir); } if ((!&Scan_Build($Processing)) && (!$Logging)) { if ($Verbose) { printf("passed...\n\n"); } } undef(@BaseLineData); undef(@LiveData); } printf(DB "# - - - - -> END Directories <- - - - -\n"); } else { # No DIRECTORY directives in config file if ((!$BaseLine) && (!$Logging)) { if ($Verbose) { printf("\nPROGRESS: No directories specified in config file validation \n"); } } if ($Verbose) { printf("\nNo Directories specified for baseline\n"); } } if ($ReportFail != 0 ) { #Return 1 for not all passes exit(1); } } else # Not automatic mode { if (!$BaseLine) { if (!$Logging) { if ($Verbose) { printf("\nPROGRESS: validating integrity of %s\nSTATUS: ", $Dir); } } } else # Not automatic, but baselining (included from original fcheck) { if ($Verbose) { printf("\nbuilding baseline for %s\n", $Dir); } if (-d $Dir ) { $Processing="Directory"; &Scan_Build($Processing); } elsif (-f $Dir ) { $Processing="Files"; &Scan_Build($Processing); } else { if (!$Logging) { printf("\nError: Command line argument: $Dir not a file or directory \n"); } &Help; } } } close(DB); if (!$DOS) { chmod 0600,$DBFile ; }